package routingcodes

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/jackc/pgx/v5"

	"github.com/niels/banking-app/backend/internal/domain"
)

const (
	RuleSourceSeedExample      = "seed_example"
	RuleSourceProviderContract = "provider_contract"
	RuleSourceInternalPolicy   = "internal_policy"

	ApprovalStatusDraft    = "draft"
	ApprovalStatusApproved = "approved"
	ApprovalStatusRetired  = "retired"
)

var providerPattern = regexp.MustCompile(`^[a-z0-9_:-]{2,80}$`)

type PaymentRoute struct {
	Provider             string
	Country              string
	Network              string
	Currency             string
	RoutingCodes         []domain.RoutingCode
	RequireContractRules bool
}

type Requirement struct {
	ID                    string          `json:"id"`
	Provider              string          `json:"provider"`
	Country               string          `json:"country"`
	Network               string          `json:"network"`
	Currency              string          `json:"currency"`
	CodeType              string          `json:"code_type"`
	Required              bool            `json:"required"`
	Active                bool            `json:"active"`
	Priority              int             `json:"priority"`
	Description           string          `json:"description"`
	RuleSource            string          `json:"rule_source"`
	ContractReference     string          `json:"contract_reference,omitempty"`
	ContractVersion       string          `json:"contract_version,omitempty"`
	ContractSignedAt      string          `json:"contract_signed_at,omitempty"`
	EffectiveFrom         string          `json:"effective_from"`
	EffectiveTo           string          `json:"effective_to,omitempty"`
	ApprovalStatus        string          `json:"approval_status"`
	ApprovedByAdminUserID string          `json:"approved_by_admin_user_id,omitempty"`
	ApprovedAt            *time.Time      `json:"approved_at,omitempty"`
	RetiredAt             *time.Time      `json:"retired_at,omitempty"`
	Metadata              json.RawMessage `json:"metadata"`
	CreatedAt             time.Time       `json:"created_at"`
	UpdatedAt             time.Time       `json:"updated_at"`
}

type RequirementParams struct {
	Provider          string
	Country           string
	Network           string
	Currency          string
	CodeType          string
	Required          bool
	Active            bool
	Priority          int
	Description       string
	RuleSource        string
	ContractReference string
	ContractVersion   string
	ContractSignedAt  string
	EffectiveFrom     string
	EffectiveTo       string
	ApprovalStatus    string
	Metadata          []byte
}

func ValidatePaymentRoute(ctx context.Context, tx pgx.Tx, route PaymentRoute) error {
	provider := strings.ToLower(strings.TrimSpace(route.Provider))
	country := strings.ToUpper(strings.TrimSpace(route.Country))
	network := strings.ToLower(strings.TrimSpace(route.Network))
	currency := strings.ToUpper(strings.TrimSpace(route.Currency))

	if provider == "" || country == "" || network == "" || currency == "" {
		return fmt.Errorf("%w: payment route provider, country, network and currency are required", domain.ErrValidation)
	}

	rows, err := tx.Query(ctx, `
		SELECT code_type, rule_source, COALESCE(contract_reference, '')
		FROM routing_code_requirements
		WHERE provider = $1
			AND country = $2
			AND network = $3
			AND active = true
			AND required = true
			AND approval_status = 'approved'
			AND effective_from <= CURRENT_DATE
			AND (effective_to IS NULL OR effective_to >= CURRENT_DATE)
			AND (currency = '*' OR currency = $4)
		ORDER BY CASE WHEN currency = $4 THEN 0 ELSE 1 END, priority, code_type
	`, provider, country, network, currency)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil
		}
		return err
	}
	defer rows.Close()

	required := []string{}
	seenRequired := map[string]struct{}{}
	for rows.Next() {
		var codeType, ruleSource, contractReference string
		if err := rows.Scan(&codeType, &ruleSource, &contractReference); err != nil {
			return err
		}
		if route.RequireContractRules && ruleSource == RuleSourceSeedExample {
			return fmt.Errorf(
				"%w: provider %s uses seed example routing-code requirements; replace them with approved provider contract rules",
				domain.ErrValidation,
				provider,
			)
		}
		if route.RequireContractRules && provider != "local_sepa" && ruleSource != RuleSourceProviderContract {
			return fmt.Errorf(
				"%w: provider %s requires approved provider_contract routing-code requirements",
				domain.ErrValidation,
				provider,
			)
		}
		if route.RequireContractRules && strings.TrimSpace(contractReference) == "" {
			return fmt.Errorf("%w: routing-code requirement contract_reference is required for provider %s", domain.ErrValidation, provider)
		}
		if _, exists := seenRequired[codeType]; exists {
			continue
		}
		seenRequired[codeType] = struct{}{}
		required = append(required, codeType)
	}
	if err := rows.Err(); err != nil {
		return err
	}
	if len(required) == 0 {
		if route.RequireContractRules {
			return fmt.Errorf(
				"%w: no approved effective routing-code contract rules found for provider %s %s/%s/%s",
				domain.ErrValidation,
				provider,
				country,
				network,
				currency,
			)
		}
		return nil
	}

	available := map[string]struct{}{}
	for _, code := range route.RoutingCodes {
		if strings.EqualFold(code.Country, country) && strings.EqualFold(code.Network, network) && code.Status != "deleted" {
			available[strings.ToLower(strings.TrimSpace(code.CodeType))] = struct{}{}
		}
	}
	for _, codeType := range required {
		if _, ok := available[codeType]; !ok {
			return fmt.Errorf(
				"%w: routing code %s is required for provider %s %s/%s/%s",
				domain.ErrValidation,
				codeType,
				provider,
				country,
				network,
				currency,
			)
		}
	}
	return nil
}

func NormalizeRequirementParams(params RequirementParams) (RequirementParams, error) {
	params.Provider = strings.ToLower(strings.TrimSpace(params.Provider))
	params.Country = strings.ToUpper(strings.TrimSpace(params.Country))
	params.Network = strings.ToLower(strings.TrimSpace(params.Network))
	params.Currency = strings.ToUpper(strings.TrimSpace(params.Currency))
	params.CodeType = strings.ToLower(strings.TrimSpace(params.CodeType))
	params.Description = strings.TrimSpace(params.Description)
	params.RuleSource = strings.ToLower(strings.TrimSpace(params.RuleSource))
	params.ContractReference = strings.TrimSpace(params.ContractReference)
	params.ContractVersion = strings.TrimSpace(params.ContractVersion)
	params.ContractSignedAt = strings.TrimSpace(params.ContractSignedAt)
	params.EffectiveFrom = strings.TrimSpace(params.EffectiveFrom)
	params.EffectiveTo = strings.TrimSpace(params.EffectiveTo)
	params.ApprovalStatus = strings.ToLower(strings.TrimSpace(params.ApprovalStatus))

	if !providerPattern.MatchString(params.Provider) {
		return RequirementParams{}, fmt.Errorf("%w: provider must be 2-80 lowercase letters, numbers, _, : or -", domain.ErrValidation)
	}
	if !countryPattern.MatchString(params.Country) {
		return RequirementParams{}, fmt.Errorf("%w: country must be ISO-3166 alpha-2", domain.ErrValidation)
	}
	if _, ok := allowedNetworks[params.Network]; !ok {
		return RequirementParams{}, fmt.Errorf("%w: network is invalid", domain.ErrValidation)
	}
	if params.Currency == "" {
		params.Currency = "*"
	}
	if params.Currency != "*" {
		if err := domain.ValidateCurrency(params.Currency); err != nil {
			return RequirementParams{}, err
		}
	}
	if _, ok := allowedTypes[params.CodeType]; !ok {
		return RequirementParams{}, fmt.Errorf("%w: code_type is invalid", domain.ErrValidation)
	}
	if params.Priority < 0 {
		return RequirementParams{}, fmt.Errorf("%w: priority cannot be negative", domain.ErrValidation)
	}
	if params.RuleSource == "" {
		params.RuleSource = RuleSourceProviderContract
	}
	switch params.RuleSource {
	case RuleSourceSeedExample, RuleSourceProviderContract, RuleSourceInternalPolicy:
	default:
		return RequirementParams{}, fmt.Errorf("%w: rule_source is invalid", domain.ErrValidation)
	}
	if params.ApprovalStatus == "" {
		params.ApprovalStatus = ApprovalStatusDraft
	}
	switch params.ApprovalStatus {
	case ApprovalStatusDraft, ApprovalStatusApproved, ApprovalStatusRetired:
	default:
		return RequirementParams{}, fmt.Errorf("%w: approval_status is invalid", domain.ErrValidation)
	}
	if params.ApprovalStatus == ApprovalStatusApproved {
		if params.RuleSource == RuleSourceProviderContract && params.ContractReference == "" {
			return RequirementParams{}, fmt.Errorf("%w: contract_reference is required for approved provider contract rules", domain.ErrValidation)
		}
		if params.EffectiveFrom == "" {
			params.EffectiveFrom = time.Now().UTC().Format(time.DateOnly)
		}
	}
	if params.ContractSignedAt != "" {
		if _, err := time.Parse(time.DateOnly, params.ContractSignedAt); err != nil {
			return RequirementParams{}, fmt.Errorf("%w: contract_signed_at must be YYYY-MM-DD", domain.ErrValidation)
		}
	}
	var effectiveFrom time.Time
	if params.EffectiveFrom != "" {
		parsed, err := time.Parse(time.DateOnly, params.EffectiveFrom)
		if err != nil {
			return RequirementParams{}, fmt.Errorf("%w: effective_from must be YYYY-MM-DD", domain.ErrValidation)
		}
		effectiveFrom = parsed
	}
	if params.EffectiveTo != "" {
		parsed, err := time.Parse(time.DateOnly, params.EffectiveTo)
		if err != nil {
			return RequirementParams{}, fmt.Errorf("%w: effective_to must be YYYY-MM-DD", domain.ErrValidation)
		}
		if !effectiveFrom.IsZero() && parsed.Before(effectiveFrom) {
			return RequirementParams{}, fmt.Errorf("%w: effective_to cannot be before effective_from", domain.ErrValidation)
		}
	}
	if len(params.Metadata) == 0 {
		params.Metadata = []byte("{}")
	}
	if !json.Valid(params.Metadata) {
		return RequirementParams{}, fmt.Errorf("%w: metadata must be valid JSON", domain.ErrValidation)
	}
	return params, nil
}
