package config

import "github.com/go-playground/validator/v10"

type PoliciesConfig struct {
	Global    Global                `yaml:"global"`
	Endpoints []EndpointConfig      `yaml:"endpoints" validate:"dive"`
	Accounts  map[AccountID]Account `yaml:"accounts"  validate:"dive"`
	Exporters Exporters             `yaml:"exporters"`
}

type (
	AccountID    string
	QuotaGroupID int
)

type Account struct {
	Tokens         []Token        `yaml:"tokens"`
	Authentication Authentication `yaml:"authentication"`
}

type AuthType int

const (
	AuthUndefined AuthType = iota
	AuthBasic
	AuthAPI
	AuthOAuth
)

type Authentication struct {
	OAuth    *OAuth     `yaml:"o_auth"`
	APIKey   *APIKey    `yaml:"api_key"`
	Basic    *BasicAuth `yaml:"basic"`
	AuthType AuthType
}

type BasicAuth struct {
	Username string `yaml:"username" validate:"required"`
	Password string `yaml:"password" validate:"required"`
}

type OAuth struct {
	Tokens []Body `yaml:"tokens"`
}

type APIKey struct {
	Tokens []Header `yaml:"tokens"`
}

type Token struct {
	Header *Header `yaml:"header" validate:"required"`
}

type Body struct {
	Name  string `yaml:"name"  validate:"required"`
	Value string `yaml:"value" validate:"required"`
}

type Header struct {
	Name  string `yaml:"name"  validate:"required"`
	Value string `yaml:"value" validate:"required"`
}

type Exporters struct {
	File       *FileExporterConfig    `yaml:"file"`
	S3         *S3ExporterConfig      `yaml:"s3"`
	S3Minio    *S3MinioExporterConfig `yaml:"s3_minio"`
	Prometheus *PrometheusConfig      `yaml:"prometheus"`
}

type Global struct {
	Remedies  []Remedy    `yaml:"remedies"  validate:"dive"`
	Diagnosis []Diagnosis `yaml:"diagnosis" validate:"dive"`
}

type EndpointConfig struct {
	URL       string      `yaml:"url"`
	Method    string      `yaml:"method"    validate:"required"`
	Remedies  []Remedy    `yaml:"remedies"  validate:"dive"`
	Diagnosis []Diagnosis `yaml:"diagnosis" validate:"dive"`
}

// Remedy

type Remedy struct {
	Enabled    bool         `yaml:"enabled"`
	Name       string       `yaml:"name"    validate:"required"`
	Config     RemedyConfig `yaml:"config"`
	remedyType RemedyType
}

type RemedyConfig struct {
	Caching                    *CachingConfig                    `yaml:"caching"`
	ResponseBasedThrottling    *ResponseBasedThrottlingConfig    `yaml:"response_based_throttling"`
	StrategyBasedThrottling    *StrategyBasedThrottlingConfig    `yaml:"strategy_based_throttling"`
	StrategyBasedQueue         *StrategyBasedQueueConfig         `yaml:"strategy_based_queue"`
	ConcurrencyBasedThrottling *ConcurrencyBasedThrottlingConfig `yaml:"concurrency_based_throttling"`
	AccountOrchestration       *AccountOrchestrationConfig       `yaml:"account_orchestration"`
	FixedResponse              *FixedResponseConfig              `yaml:"fixed_response"`
	Retry                      *RetryConfig                      `yaml:"retry"`
	Authentication             *AuthConfig                       `yaml:"authentication"`
}

type RemedyType int

const (
	RemedyUndefined RemedyType = iota
	RemedyCaching
	RemedyResponseBasedThrottling
	RemedyStrategyBasedThrottling
	RemedyConcurrencyBasedThrottling
	RemedyStrategyBasedQueue
	RemedyAccountOrchestration
	RemedyFixedResponse
	RemedyRetry
	RemedyAuth
)

type AuthConfig struct {
	Account AccountID `yaml:"account" validate:"required"`
}

type PayloadPath struct {
	PayloadType payloadLiteral `yaml:"payload_type"`
	Path        string         `yaml:"path"`
}

type CachingConfig struct {
	RequestPayloadPaths   []PayloadPath `yaml:"request_payload_paths"    validate:"dive"`
	TTLSeconds            float32       `yaml:"ttl_seconds"`
	MaxRecordSizeBytes    int           `yaml:"max_record_size_bytes"`
	MaxCacheSizeMegabytes float32       `yaml:"max_cache_size_megabytes"`
}

type ResponseBasedThrottlingConfig struct {
	QuotaGroup       int            `yaml:"quota_group"`
	RetryAfterHeader string         `yaml:"retry_after_header"`
	RetryAfterType   RetryAfterType `yaml:"retry_after_type"`
	RelevantStatuses []int          `yaml:"relevant_statuses"  validate:"required,dive,min=100,max=599"` //nolint:lll
}

type StrategyBasedThrottlingConfig struct {
	AllowedRequestCount  int64                 `yaml:"allowed_request_count"`
	WindowSizeInSeconds  int                   `yaml:"window_size_in_seconds"`
	GroupQuotaAllocation *GroupQuotaAllocation `yaml:"group_quota_allocation"`
	ResponseStatusCode   int                   `yaml:"response_status_code"`
	SpilloverConfig      SpilloverConfig       `yaml:"spillover_config"`
}

type SpilloverConfig struct {
	Enabled    bool `yaml:"enabled"`
	RenewOnDay int  `yaml:"renew_on_day"`
}

type StrategyBasedQueueConfig struct {
	AllowedRequestCount int64                `yaml:"allowed_request_count"  validate:"required,gte=1"`
	WindowSizeInSeconds int                  `yaml:"window_size_in_seconds" validate:"required,gte=1"`
	ResponseStatusCode  int                  `yaml:"response_status_code"   validate:"required,min=100,max=599"` //nolint:lll
	TTLSeconds          float32              `yaml:"ttl_seconds"            validate:"required,gte=1"`
	QueueSize           int64                `yaml:"queue_size"             validate:"required,gte=1"`
	Prioritization      *GroupPrioritization `yaml:"prioritization"`
}

type ConcurrencyBasedThrottlingConfig struct {
	MaxConcurrentRequests int `yaml:"max_concurrent_requests"`
	ResponseStatusCode    int `yaml:"response_status_code"    validate:"required,min=100,max=599"`
}

type AccountOrchestrationConfig struct {
	RoundRobin []AccountID `yaml:"round_robin" validate:"required"`
}

type FixedResponseConfig struct {
	StatusCode int `yaml:"status_code" validate:"required,min=100,max=599"`
}

type RetryConfig struct {
	Attempts               int                   `yaml:"attempts"`
	InitialCooldownSeconds int                   `yaml:"initial_cooldown_seconds"`
	CooldownMultiplier     int                   `yaml:"cooldown_multiplier"`
	Conditions             RetryConfigConditions `yaml:"conditions"`
}

type RetryConfigConditions struct {
	StatusCode []Range[int] `yaml:"status_code" validate:"required"`
}

type Range[T any] struct {
	From T `yaml:"from"`
	To   T `yaml:"to"`
}
type GroupBy struct {
	HeaderName string `yaml:"header_name"`
}
type GroupQuotaAllocation struct {
	GroupBy                     *GroupBy                         `yaml:"group_by"                      validate:"required"` //nolint:lll
	Groups                      []QuotaAllocation                `yaml:"groups"                        validate:"dive"`     //nolint:lll
	Default                     defaultQuotaGroupBehaviorLiteral `yaml:"default"`
	DefaultAllocationPercentage float64                          `yaml:"default_allocation_percentage" validate:"gte=0"` //nolint:lll
}

type GroupPrioritization struct {
	GroupBy GroupBy                   `yaml:"group_by" validate:"required"`
	Groups  map[string]Prioritization `yaml:"groups"   validate:"dive"`
}

type Prioritization struct {
	// `priority`` is taken as float64 but is validated to be an actual integer
	// in order to avoid runtime type conversion (int->float64)
	Priority float64 `yaml:"priority" validate:"validateInt,gte=0"`
}

type (
	defaultQuotaGroupBehaviorLiteral = string
	DefaultQuotaGroupBehavior        int
)

const (
	DefaultQuotaGroupBehaviorUndefined DefaultQuotaGroupBehavior = iota
	DefaultQuotaGroupBehaviorAllow
	DefaultQuotaGroupBehaviorBlock
	DefaultQuotaGroupBehaviorUseDefaultAllocation
)

func (behavior DefaultQuotaGroupBehavior) String() string {
	var res string
	switch behavior {
	case DefaultQuotaGroupBehaviorAllow:
		res = "allow"
	case DefaultQuotaGroupBehaviorBlock:
		res = "block"
	case DefaultQuotaGroupBehaviorUseDefaultAllocation:
		res = "use_default_allocation"
	case DefaultQuotaGroupBehaviorUndefined:
		res = "undefined"
	}

	return res
}

type QuotaAllocation struct {
	GroupHeaderValue     string  `yaml:"group_header_value"`
	AllocationPercentage float64 `yaml:"allocation_percentage" validate:"gte=0"`
}

type RetryAfterType int

const (
	RetryAfterUndefined RetryAfterType = iota
	RetryAfterAbsoluteEpoch
	RetryAfterRelativeSeconds
)

// Diagnosis

type Diagnosis struct {
	Enabled bool            `yaml:"enabled"`
	Name    string          `yaml:"name"    validate:"required"`
	Config  DiagnosisConfig `yaml:"config"  validate:"required"`
	Export  string          `yaml:"export"  validate:"required"`

	exporterType  ExporterType
	diagnosisType DiagnosisType
}

type DiagnosisConfig struct {
	HARExporter      *HARExporterConfig      `yaml:"har_exporter"`
	MetricsCollector *MetricsCollectorConfig `yaml:"metrics_collector"`
	Void             *VoidConfig             `yaml:"void"`
}

type HARExporterConfig struct {
	TransactionMaxSize  int         `yaml:"transaction_max_size"`
	Obfuscate           Obfuscate   `yaml:"obfuscate"`
	RequestHeaderNames  HeaderNames `yaml:"request_header_names"`
	ResponseHeaderNames HeaderNames `yaml:"response_header_names"`
}

type Obfuscate struct {
	Enabled    bool                  `yaml:"enabled"`
	Exclusions ObfuscationExclusions `yaml:"exclusions"`
}

type ObfuscationExclusions struct {
	QueryParams       []string `yaml:"query_params"`
	PathParams        []string `yaml:"path_params"`
	RequestHeaders    []string `yaml:"request_headers"`
	ResponseHeaders   []string `yaml:"response_headers"`
	RequestBodyPaths  []string `yaml:"request_body_paths"`
	ResponseBodyPaths []string `yaml:"response_body_paths"`
}

type HeaderNames struct {
	ContentEncoding string `yaml:"content_encoding"`
}

type MetricsCollectorConfig struct {
	RequestHeaderNames  []string  `yaml:"request_header_names"`
	ResponseHeaderNames []string  `yaml:"response_header_names"`
	Counters            []Counter `yaml:"counters"`
}

type (
	payloadLiteral = string
	Payload        int
)

const (
	PayloadUndefined Payload = iota
	PayloadResponseHeaders
	PayloadRequestPathParams
)

type Counter struct {
	NameSuffix string         `yaml:"name_suffix"`
	Payload    payloadLiteral `yaml:"payload"`
	Key        string         `yaml:"key"`
}

type VoidConfig struct{}

type DiagnosisType int

const (
	DiagnosisUndefined DiagnosisType = iota
	DiagnosisHARExporter
	DiagnosisMetricsCollector
	DiagnosisVoid
)

// Export

type ExporterType int

const (
	ExporterUndefined ExporterType = iota
	ExporterFile
	ExporterS3
	ExporterS3Minio
	ExporterPrometheus
)

type ExporterName = string

const (
	ExporterNameUndefined  ExporterName = "undefined"
	ExporterNameFile       ExporterName = "file"
	ExporterNameS3         ExporterName = "s3"
	ExporterNameS3Minio    ExporterName = "s3_minio"
	ExporterNamePrometheus ExporterName = "prometheus"
)

func (exporterType ExporterType) Name() ExporterName {
	var res string
	switch exporterType {
	case ExporterFile:
		res = ExporterNameFile
	case ExporterS3:
		res = ExporterNameS3
	case ExporterS3Minio:
		res = ExporterNameS3Minio
	case ExporterPrometheus:
		res = ExporterNamePrometheus
	case ExporterUndefined:
		res = ExporterNameUndefined
	}

	return res
}

type FileExporterConfig struct {
	FileDir  string `yaml:"file_dir"  validate:"required"`
	FileName string `yaml:"file_name" validate:"required"`
}

type S3ExporterConfig struct {
	BucketName string `yaml:"bucket_name" validate:"required"`
	Region     string `yaml:"region"      validate:"required"`
}

type S3MinioExporterConfig struct {
	BucketName string `yaml:"bucket_name" validate:"required"`
	URL        string `yaml:"url"         validate:"required"`
}

type PrometheusConfig struct {
	BucketBoundaries []float64 `yaml:"bucket_boundaries"`
}

// use a single instance of Validate, it caches struct info
var Validate *validator.Validate = validator.New()
