package cloudflare

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/goccy/go-json"
)

type TeamsRuleSettings struct {
	// list of ipv4 or ipv6 ips to override with, when action is set to dns override
	OverrideIPs []string `json:"override_ips"`

	// show this string at block page caused by this rule
	BlockReason string `json:"block_reason"`

	// host name to override with when action is set to dns override. Can not be used with OverrideIPs
	OverrideHost string `json:"override_host"`

	// settings for browser isolation actions
	BISOAdminControls *TeamsBISOAdminControlSettings `json:"biso_admin_controls"`

	// settings for l4(network) level overrides
	L4Override *TeamsL4OverrideSettings `json:"l4override"`

	// settings for adding headers to http requests
	AddHeaders http.Header `json:"add_headers"`

	// settings for session check in allow action
	CheckSession *TeamsCheckSessionSettings `json:"check_session"`

	// Enable block page on rules with action block
	BlockPageEnabled bool `json:"block_page_enabled"`

	// whether to disable dnssec validation for allow action
	InsecureDisableDNSSECValidation bool `json:"insecure_disable_dnssec_validation"`

	// settings for rules with egress action
	EgressSettings *EgressSettings `json:"egress"`

	// DLP payload logging configuration
	PayloadLog *TeamsDlpPayloadLogSettings `json:"payload_log"`

	//AuditSsh Settings
	AuditSSH *AuditSSHRuleSettings `json:"audit_ssh"`

	// Turns on ip category based filter on dns if the rule contains dns category checks
	IPCategories bool `json:"ip_categories"`

	// Turns on for explicitly ignoring cname domain category matches
	IgnoreCNAMECategoryMatches *bool `json:"ignore_cname_category_matches"`

	// Allow parent MSP accounts to enable bypass their children's rules. Do not set them for non MSP accounts.
	AllowChildBypass *bool `json:"allow_child_bypass,omitempty"`

	// Allow child MSP accounts to bypass their parent's rules. Do not set them for non MSP accounts.
	BypassParentRule *bool `json:"bypass_parent_rule,omitempty"`

	// Action taken when an untrusted origin certificate error occurs in a http allow rule
	UntrustedCertSettings *UntrustedCertSettings `json:"untrusted_cert"`

	// Specifies that a resolver policy should use Cloudflare's DNS Resolver.
	ResolveDnsThroughCloudflare *bool `json:"resolve_dns_through_cloudflare,omitempty"`

	// Resolver policy settings.
	DnsResolverSettings *TeamsDnsResolverSettings `json:"dns_resolvers,omitempty"`

	ResolveDnsInternallySettings *TeamsResolveDnsInternallySettings `json:"resolve_dns_internally,omitempty"`

	NotificationSettings *TeamsNotificationSettings `json:"notification_settings"`
	Quarantine           *TeamsQuarantine           `json:"quarantine,omitempty"`
	ForensicCopySettings *TeamsForensicCopySettings `json:"forensic_copy,omitempty"`
}

type TeamsForensicCopySettings struct {
	Enabled bool `json:"enabled"`
}

type TeamsQuarantine struct {
	FileTypes []FileType `json:"file_types"`
}

type FileType = string

type TeamsGatewayUntrustedCertAction string

const (
	UntrustedCertPassthrough TeamsGatewayUntrustedCertAction = "pass_through"
	UntrustedCertBlock       TeamsGatewayUntrustedCertAction = "block"
	UntrustedCertError       TeamsGatewayUntrustedCertAction = "error"
)

type UntrustedCertSettings struct {
	Action TeamsGatewayUntrustedCertAction `json:"action"`
}

type TeamsNotificationSettings struct {
	Enabled    *bool  `json:"enabled,omitempty"`
	Message    string `json:"msg"`
	SupportURL string `json:"support_url"`
}

type AuditSSHRuleSettings struct {
	CommandLogging bool `json:"command_logging"`
}

type EgressSettings struct {
	Ipv6Range    string `json:"ipv6"`
	Ipv4         string `json:"ipv4"`
	Ipv4Fallback string `json:"ipv4_fallback"`
}

// TeamsL4OverrideSettings used in l4 filter type rule with action set to override.
type TeamsL4OverrideSettings struct {
	IP   string `json:"ip,omitempty"`
	Port int    `json:"port,omitempty"`
}

type TeamsBISOAdminControlSettings struct {
	DisablePrinting             bool                                    `json:"dp"`
	DisableCopyPaste            bool                                    `json:"dcp"`
	DisableDownload             bool                                    `json:"dd"`
	DisableUpload               bool                                    `json:"du"`
	DisableKeyboard             bool                                    `json:"dk"`
	DisableClipboardRedirection bool                                    `json:"dcr"`
	Copy                        TeamsTeamsBISOAdminControlSettingsValue `json:"copy"`
	Download                    TeamsTeamsBISOAdminControlSettingsValue `json:"download"`
	Keyboard                    TeamsTeamsBISOAdminControlSettingsValue `json:"keyboard"`
	Paste                       TeamsTeamsBISOAdminControlSettingsValue `json:"paste"`
	Printing                    TeamsTeamsBISOAdminControlSettingsValue `json:"printing"`
	Upload                      TeamsTeamsBISOAdminControlSettingsValue `json:"upload"`
	Version                     TeamsBISOAdminControlSettingsVersion    `json:"version"`
}

type TeamsBISOAdminControlSettingsVersion string

const (
	TeamsBISOAdminControlSettingsV1 TeamsBISOAdminControlSettingsVersion = "v1"
	TeamsBISOAdminControlSettingsV2 TeamsBISOAdminControlSettingsVersion = "v2"
)

type TeamsTeamsBISOAdminControlSettingsValue string

const (
	TeamsBISOAdminControlEnabled    TeamsTeamsBISOAdminControlSettingsValue = "enabled"
	TeamsBISOAdminControlDisabled   TeamsTeamsBISOAdminControlSettingsValue = "disabled"
	TeamsBISOAdminControlRemoteOnly TeamsTeamsBISOAdminControlSettingsValue = "remote_only"
)

type TeamsCheckSessionSettings struct {
	Enforce  bool     `json:"enforce"`
	Duration Duration `json:"duration"`
}

type (
	TeamsDnsResolverSettings struct {
		V4Resolvers []TeamsDnsResolverAddressV4 `json:"ipv4,omitempty"`
		V6Resolvers []TeamsDnsResolverAddressV6 `json:"ipv6,omitempty"`
	}

	TeamsDnsResolverAddressV4 struct {
		TeamsDnsResolverAddress
	}

	TeamsDnsResolverAddressV6 struct {
		TeamsDnsResolverAddress
	}

	TeamsDnsResolverAddress struct {
		IP                         string `json:"ip"`
		Port                       *int   `json:"port,omitempty"`
		VnetID                     string `json:"vnet_id,omitempty"`
		RouteThroughPrivateNetwork *bool  `json:"route_through_private_network,omitempty"`
	}

	TeamsResolveDnsInternallySettings struct {
		ViewID   string                                    `json:"view_id"`
		Fallback TeamsResolveDnsInternallyFallbackStrategy `json:"fallback"`
	}

	TeamsResolveDnsInternallyFallbackStrategy string
)

const (
	None      TeamsResolveDnsInternallyFallbackStrategy = "none"
	PublicDns TeamsResolveDnsInternallyFallbackStrategy = "public_dns"
)

type TeamsDlpPayloadLogSettings struct {
	Enabled bool `json:"enabled"`
}

type TeamsFilterType string

type TeamsGatewayAction string

const (
	HttpFilter        TeamsFilterType = "http"
	DnsFilter         TeamsFilterType = "dns"
	L4Filter          TeamsFilterType = "l4"
	EgressFilter      TeamsFilterType = "egress"
	DnsResolverFilter TeamsFilterType = "dns_resolver"
)

const (
	Allow        TeamsGatewayAction = "allow"        // dns|http|l4
	Block        TeamsGatewayAction = "block"        // dns|http|l4
	SafeSearch   TeamsGatewayAction = "safesearch"   // dns
	YTRestricted TeamsGatewayAction = "ytrestricted" // dns
	On           TeamsGatewayAction = "on"           // http
	Off          TeamsGatewayAction = "off"          // http
	Scan         TeamsGatewayAction = "scan"         // http
	NoScan       TeamsGatewayAction = "noscan"       // http
	Isolate      TeamsGatewayAction = "isolate"      // http
	NoIsolate    TeamsGatewayAction = "noisolate"    // http
	Override     TeamsGatewayAction = "override"     // http
	L4Override   TeamsGatewayAction = "l4_override"  // l4
	Egress       TeamsGatewayAction = "egress"       // egress
	AuditSSH     TeamsGatewayAction = "audit_ssh"    // l4
	Resolve      TeamsGatewayAction = "resolve"      // resolve
)

func TeamsRulesActionValues() []string {
	return []string{
		string(Allow),
		string(Block),
		string(SafeSearch),
		string(YTRestricted),
		string(On),
		string(Off),
		string(Scan),
		string(NoScan),
		string(Isolate),
		string(NoIsolate),
		string(Override),
		string(L4Override),
		string(Egress),
		string(AuditSSH),
		string(Resolve),
	}
}

func TeamsRulesUntrustedCertActionValues() []string {
	return []string{
		string(UntrustedCertPassthrough),
		string(UntrustedCertBlock),
		string(UntrustedCertError),
	}
}

// TeamsRule represents an Teams wirefilter rule.
type TeamsRule struct {
	ID            string               `json:"id,omitempty"`
	CreatedAt     *time.Time           `json:"created_at,omitempty"`
	UpdatedAt     *time.Time           `json:"updated_at,omitempty"`
	DeletedAt     *time.Time           `json:"deleted_at,omitempty"`
	Name          string               `json:"name"`
	Description   string               `json:"description"`
	Precedence    uint64               `json:"precedence"`
	Enabled       bool                 `json:"enabled"`
	Action        TeamsGatewayAction   `json:"action"`
	Filters       []TeamsFilterType    `json:"filters"`
	Traffic       string               `json:"traffic"`
	Identity      string               `json:"identity"`
	DevicePosture string               `json:"device_posture"`
	Version       uint64               `json:"version"`
	RuleSettings  TeamsRuleSettings    `json:"rule_settings,omitempty"`
	Schedule      *TeamsRuleSchedule   `json:"schedule,omitempty"`   // only available at DNS rules
	Expiration    *TeamsRuleExpiration `json:"expiration,omitempty"` // only available at DNS rules
}

type TeamsRuleExpiration struct {
	ExpiresAt *time.Time `json:"expires_at"`
	Duration  *uint64    `json:"duration,omitempty"` // read only
	Expired   bool       `json:"expired"`            // read only
}

// format HH:MM,HH:MM,....,HH:MM
type TeamsScheduleTimes string

type TeamsRuleSchedule struct {
	Monday    TeamsScheduleTimes `json:"mon,omitempty"`
	Tuesday   TeamsScheduleTimes `json:"tue,omitempty"`
	Wednesday TeamsScheduleTimes `json:"wed,omitempty"`
	Thursday  TeamsScheduleTimes `json:"thu,omitempty"`
	Friday    TeamsScheduleTimes `json:"fri,omitempty"`
	Saturday  TeamsScheduleTimes `json:"sat,omitempty"`
	Sunday    TeamsScheduleTimes `json:"sun,omitempty"`
	TimeZone  string             `json:"time_zone,omitempty"` // default to user TZ based on the user IP location, fall backs to colo TZ
}

// TeamsRuleResponse is the API response, containing a single rule.
type TeamsRuleResponse struct {
	Response
	Result TeamsRule `json:"result"`
}

// TeamsRuleResponse is the API response, containing an array of rules.
type TeamsRulesResponse struct {
	Response
	Result []TeamsRule `json:"result"`
}

// TeamsRulePatchRequest is used to patch an existing rule.
type TeamsRulePatchRequest struct {
	ID           string             `json:"id"`
	Name         string             `json:"name"`
	Description  string             `json:"description"`
	Precedence   uint64             `json:"precedence"`
	Enabled      bool               `json:"enabled"`
	Action       TeamsGatewayAction `json:"action"`
	RuleSettings TeamsRuleSettings  `json:"rule_settings,omitempty"`
}

// TeamsRules returns all rules within an account.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsRules(ctx context.Context, accountID string) ([]TeamsRule, error) {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []TeamsRule{}, err
	}

	var teamsRulesResponse TeamsRulesResponse
	err = json.Unmarshal(res, &teamsRulesResponse)
	if err != nil {
		return []TeamsRule{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return teamsRulesResponse.Result, nil
}

// TeamsRule returns the rule with rule ID in the URL.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsRule(ctx context.Context, accountID string, ruleId string) (TeamsRule, error) {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return TeamsRule{}, err
	}

	var teamsRuleResponse TeamsRuleResponse
	err = json.Unmarshal(res, &teamsRuleResponse)
	if err != nil {
		return TeamsRule{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return teamsRuleResponse.Result, nil
}

// TeamsCreateRule creates a rule with wirefilter expression.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsCreateRule(ctx context.Context, accountID string, rule TeamsRule) (TeamsRule, error) {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rule)
	if err != nil {
		return TeamsRule{}, err
	}

	var teamsRuleResponse TeamsRuleResponse
	err = json.Unmarshal(res, &teamsRuleResponse)
	if err != nil {
		return TeamsRule{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return teamsRuleResponse.Result, nil
}

// TeamsUpdateRule updates a rule with wirefilter expression.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsUpdateRule(ctx context.Context, accountID string, ruleId string, rule TeamsRule) (TeamsRule, error) {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId)

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, rule)
	if err != nil {
		return TeamsRule{}, err
	}

	var teamsRuleResponse TeamsRuleResponse
	err = json.Unmarshal(res, &teamsRuleResponse)
	if err != nil {
		return TeamsRule{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return teamsRuleResponse.Result, nil
}

// TeamsPatchRule patches a rule associated values.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsPatchRule(ctx context.Context, accountID string, ruleId string, rule TeamsRulePatchRequest) (TeamsRule, error) {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId)

	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, rule)
	if err != nil {
		return TeamsRule{}, err
	}

	var teamsRuleResponse TeamsRuleResponse
	err = json.Unmarshal(res, &teamsRuleResponse)
	if err != nil {
		return TeamsRule{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return teamsRuleResponse.Result, nil
}

// TeamsDeleteRule deletes a rule.
//
// API reference: https://api.cloudflare.com/#teams-rules-properties
func (api *API) TeamsDeleteRule(ctx context.Context, accountID string, ruleId string) error {
	uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId)

	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}

	return nil
}
