package legal

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgxpool"

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

const (
	TypeRegulatedActivity   = "regulated_activity_decision"
	TypeOperatingModel      = "operating_model"
	TypeLegalMemo           = "legal_memo"
	TypeProductTermApproval = "product_term_approval"
	TypeProviderContract    = "provider_contract"
	TypeSafeguardingModel   = "safeguarding_model"
	TypePolicyDocument      = "policy_document"
	TypeJurisdictionRule    = "jurisdiction_product_rule"

	StatusDraft              = "draft"
	StatusInReview           = "in_review"
	StatusApproved           = "approved"
	StatusRejected           = "rejected"
	StatusBlocked            = "blocked"
	StatusSigned             = "signed"
	StatusPublished          = "published"
	StatusExpired            = "expired"
	StatusDisabled           = "disabled"
	StatusTestOnly           = "test_only"
	StatusAllowed            = "allowed"
	StatusProhibited         = "prohibited"
	StatusRequiresDisclaimer = "requires_disclaimer"
	StatusInScope            = "in_scope"
	StatusOutOfScope         = "out_of_scope"
	StatusNeedsLegalReview   = "needs_legal_review"
)

type Repository struct {
	db *pgxpool.Pool
}

type Control struct {
	ID                    string          `json:"id"`
	ControlKey            string          `json:"control_key"`
	ControlType           string          `json:"control_type"`
	Jurisdiction          string          `json:"jurisdiction"`
	ProductScope          string          `json:"product_scope,omitempty"`
	ProviderCategory      string          `json:"provider_category,omitempty"`
	ProviderName          string          `json:"provider_name,omitempty"`
	Status                string          `json:"status"`
	RiskLevel             string          `json:"risk_level"`
	OwnerTeam             string          `json:"owner_team"`
	DecisionSummary       string          `json:"decision_summary"`
	LegalMemoReference    string          `json:"legal_memo_reference,omitempty"`
	ContractReference     string          `json:"contract_reference,omitempty"`
	PolicyVersion         string          `json:"policy_version,omitempty"`
	EvidenceReference     string          `json:"evidence_reference,omitempty"`
	RequiredDisclosures   []string        `json:"required_disclosures"`
	EffectiveAt           *time.Time      `json:"effective_at,omitempty"`
	ExpiresAt             *time.Time      `json:"expires_at,omitempty"`
	Metadata              json.RawMessage `json:"metadata"`
	CreatedByAdminUserID  string          `json:"created_by_admin_user_id,omitempty"`
	ApprovedByAdminUserID string          `json:"approved_by_admin_user_id,omitempty"`
	ApprovedAt            *time.Time      `json:"approved_at,omitempty"`
	CreatedAt             time.Time       `json:"created_at"`
	UpdatedAt             time.Time       `json:"updated_at"`
}

type ControlParams struct {
	ControlKey          string
	ControlType         string
	Jurisdiction        string
	ProductScope        string
	ProviderCategory    string
	ProviderName        string
	Status              string
	RiskLevel           string
	OwnerTeam           string
	DecisionSummary     string
	LegalMemoReference  string
	ContractReference   string
	PolicyVersion       string
	EvidenceReference   string
	RequiredDisclosures []string
	EffectiveAt         *time.Time
	ExpiresAt           *time.Time
	Metadata            json.RawMessage
	AdminUserID         string
}

type Metrics struct {
	ControlsTotal            int64 `json:"controls_total"`
	LegalGatesOpen           int64 `json:"legal_gates_open"`
	ApprovedControls         int64 `json:"approved_controls"`
	ExpiredControls          int64 `json:"expired_controls"`
	PublishedPolicies        int64 `json:"published_policies"`
	BlockedJurisdictionRules int64 `json:"blocked_jurisdiction_rules"`
}

type Dashboard struct {
	Metrics         Metrics              `json:"metrics"`
	Controls        []Control            `json:"controls"`
	ProductionGates []Control            `json:"production_gates"`
	ByType          map[string][]Control `json:"by_type"`
	GeneratedAt     time.Time            `json:"generated_at"`
}

func NewRepository(db *pgxpool.Pool) *Repository {
	return &Repository{db: db}
}

func (r *Repository) Dashboard(ctx context.Context, limit int) (Dashboard, error) {
	metrics, err := r.Metrics(ctx)
	if err != nil {
		return Dashboard{}, err
	}
	controls, err := r.ListControls(ctx, "", "", "", limit)
	if err != nil {
		return Dashboard{}, err
	}
	byType := map[string][]Control{}
	gates := []Control{}
	for _, control := range controls {
		byType[control.ControlType] = append(byType[control.ControlType], control)
		if isOpenLegalGate(control) {
			gates = append(gates, control)
		}
	}
	return Dashboard{
		Metrics:         metrics,
		Controls:        controls,
		ProductionGates: gates,
		ByType:          byType,
		GeneratedAt:     time.Now().UTC(),
	}, nil
}

func (r *Repository) Metrics(ctx context.Context) (Metrics, error) {
	var metrics Metrics
	err := r.db.QueryRow(ctx, `
		SELECT
			(SELECT COUNT(*) FROM legal_market_access_controls)::bigint,
			(SELECT COUNT(*) FROM legal_market_access_controls
				WHERE control_type IN ('regulated_activity_decision', 'operating_model', 'legal_memo', 'product_term_approval', 'provider_contract', 'safeguarding_model', 'policy_document', 'jurisdiction_product_rule')
					AND status NOT IN ('approved', 'signed', 'published', 'allowed', 'out_of_scope', 'disabled', 'prohibited'))::bigint,
			(SELECT COUNT(*) FROM legal_market_access_controls WHERE status IN ('approved', 'signed', 'published', 'allowed', 'out_of_scope', 'prohibited'))::bigint,
			(SELECT COUNT(*) FROM legal_market_access_controls WHERE expires_at IS NOT NULL AND expires_at < now() AND status NOT IN ('expired', 'disabled'))::bigint,
			(SELECT COUNT(*) FROM legal_market_access_controls WHERE control_type = 'policy_document' AND status = 'published')::bigint,
			(SELECT COUNT(*) FROM legal_market_access_controls WHERE control_type = 'jurisdiction_product_rule' AND status IN ('blocked', 'prohibited'))::bigint
	`).Scan(
		&metrics.ControlsTotal,
		&metrics.LegalGatesOpen,
		&metrics.ApprovedControls,
		&metrics.ExpiredControls,
		&metrics.PublishedPolicies,
		&metrics.BlockedJurisdictionRules,
	)
	return metrics, err
}

func (r *Repository) ListControls(ctx context.Context, controlType, jurisdiction, status string, limit int) ([]Control, error) {
	limit = normalizeLimit(limit)
	controlType = strings.ToLower(strings.TrimSpace(controlType))
	jurisdiction = strings.ToUpper(strings.TrimSpace(jurisdiction))
	status = strings.ToLower(strings.TrimSpace(status))
	if controlType != "" && controlType != "all" {
		if err := validateControlType(controlType); err != nil {
			return nil, err
		}
	}
	rows, err := r.db.Query(ctx, controlSelect+`
		WHERE ($1 = '' OR $1 = 'all' OR control_type = $1)
			AND ($2 = '' OR $2 = 'ALL' OR jurisdiction = $2)
			AND ($3 = '' OR $3 = 'all' OR status = $3)
		ORDER BY
			CASE
				WHEN status IN ('blocked', 'needs_legal_review', 'in_review') THEN 0
				WHEN expires_at IS NOT NULL AND expires_at < now() THEN 1
				WHEN status IN ('draft', 'test_only') THEN 2
				ELSE 3
			END,
			updated_at DESC
		LIMIT $4
	`, controlType, jurisdiction, status, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	controls := []Control{}
	for rows.Next() {
		control, err := scanControl(rows)
		if err != nil {
			return nil, err
		}
		controls = append(controls, control)
	}
	return controls, rows.Err()
}

func (r *Repository) UpsertControl(ctx context.Context, params ControlParams) (Control, error) {
	if err := normalizeControlParams(&params); err != nil {
		return Control{}, err
	}
	row := r.db.QueryRow(ctx, `
		INSERT INTO legal_market_access_controls (
			control_key, control_type, jurisdiction, product_scope, provider_category, provider_name,
			status, risk_level, owner_team, decision_summary, legal_memo_reference, contract_reference,
			policy_version, evidence_reference, required_disclosures, effective_at, expires_at,
			metadata, created_by_admin_user_id, approved_by_admin_user_id, approved_at
		)
		VALUES (
			$1, $2, $3, $4, $5, $6,
			$7, $8, $9, $10, $11, $12,
			$13, $14, $15, $16, $17,
			$18, NULLIF($19, '')::uuid,
			CASE WHEN $7 IN ('approved', 'signed', 'published', 'allowed', 'prohibited', 'out_of_scope') THEN NULLIF($19, '')::uuid ELSE NULL END,
			CASE WHEN $7 IN ('approved', 'signed', 'published', 'allowed', 'prohibited', 'out_of_scope') THEN now() ELSE NULL END
		)
		ON CONFLICT (control_key) DO UPDATE
		SET control_type = EXCLUDED.control_type,
			jurisdiction = EXCLUDED.jurisdiction,
			product_scope = EXCLUDED.product_scope,
			provider_category = EXCLUDED.provider_category,
			provider_name = EXCLUDED.provider_name,
			status = EXCLUDED.status,
			risk_level = EXCLUDED.risk_level,
			owner_team = EXCLUDED.owner_team,
			decision_summary = EXCLUDED.decision_summary,
			legal_memo_reference = EXCLUDED.legal_memo_reference,
			contract_reference = EXCLUDED.contract_reference,
			policy_version = EXCLUDED.policy_version,
			evidence_reference = EXCLUDED.evidence_reference,
			required_disclosures = EXCLUDED.required_disclosures,
			effective_at = EXCLUDED.effective_at,
			expires_at = EXCLUDED.expires_at,
			metadata = EXCLUDED.metadata,
			approved_by_admin_user_id = CASE
				WHEN EXCLUDED.status IN ('approved', 'signed', 'published', 'allowed', 'prohibited', 'out_of_scope') THEN EXCLUDED.approved_by_admin_user_id
				ELSE legal_market_access_controls.approved_by_admin_user_id
			END,
			approved_at = CASE
				WHEN EXCLUDED.status IN ('approved', 'signed', 'published', 'allowed', 'prohibited', 'out_of_scope') THEN now()
				ELSE legal_market_access_controls.approved_at
			END
		RETURNING `+controlColumns,
		params.ControlKey,
		params.ControlType,
		params.Jurisdiction,
		params.ProductScope,
		params.ProviderCategory,
		params.ProviderName,
		params.Status,
		params.RiskLevel,
		params.OwnerTeam,
		params.DecisionSummary,
		params.LegalMemoReference,
		params.ContractReference,
		params.PolicyVersion,
		params.EvidenceReference,
		params.RequiredDisclosures,
		params.EffectiveAt,
		params.ExpiresAt,
		params.Metadata,
		params.AdminUserID,
	)
	control, err := scanControl(row)
	if err != nil && isForeignKeyViolation(err) {
		return Control{}, fmt.Errorf("%w: admin user does not exist", domain.ErrValidation)
	}
	return control, err
}

func normalizeControlParams(params *ControlParams) error {
	params.ControlKey = strings.ToLower(strings.TrimSpace(params.ControlKey))
	params.ControlType = strings.ToLower(strings.TrimSpace(params.ControlType))
	params.Jurisdiction = strings.ToUpper(strings.TrimSpace(params.Jurisdiction))
	params.ProductScope = strings.ToLower(strings.TrimSpace(params.ProductScope))
	params.ProviderCategory = strings.ToLower(strings.TrimSpace(params.ProviderCategory))
	params.ProviderName = strings.TrimSpace(params.ProviderName)
	params.Status = strings.ToLower(strings.TrimSpace(params.Status))
	params.RiskLevel = strings.ToLower(strings.TrimSpace(params.RiskLevel))
	params.OwnerTeam = strings.ToLower(strings.TrimSpace(params.OwnerTeam))
	params.DecisionSummary = strings.TrimSpace(params.DecisionSummary)
	params.LegalMemoReference = strings.TrimSpace(params.LegalMemoReference)
	params.ContractReference = strings.TrimSpace(params.ContractReference)
	params.PolicyVersion = strings.TrimSpace(params.PolicyVersion)
	params.EvidenceReference = strings.TrimSpace(params.EvidenceReference)
	if params.ControlKey == "" {
		return fmt.Errorf("%w: control_key is required", domain.ErrValidation)
	}
	if err := validateControlType(params.ControlType); err != nil {
		return err
	}
	if params.Jurisdiction == "" {
		params.Jurisdiction = "GLOBAL"
	}
	if !validJurisdiction(params.Jurisdiction) {
		return fmt.Errorf("%w: jurisdiction must be GLOBAL, EU or a country/market code", domain.ErrValidation)
	}
	if params.Status == "" {
		params.Status = StatusDraft
	}
	if !validStatus(params.Status) {
		return fmt.Errorf("%w: invalid legal control status", domain.ErrValidation)
	}
	if params.RiskLevel == "" {
		params.RiskLevel = "high"
	}
	if !validRiskLevel(params.RiskLevel) {
		return fmt.Errorf("%w: invalid risk_level", domain.ErrValidation)
	}
	if params.OwnerTeam == "" {
		params.OwnerTeam = "legal"
	}
	if !validOwnerTeam(params.OwnerTeam) {
		return fmt.Errorf("%w: invalid owner_team", domain.ErrValidation)
	}
	if params.DecisionSummary == "" {
		return fmt.Errorf("%w: decision_summary is required", domain.ErrValidation)
	}
	if err := validateTypeSpecificParams(params); err != nil {
		return err
	}
	if params.Metadata == nil || len(params.Metadata) == 0 {
		params.Metadata = json.RawMessage(`{}`)
	}
	if !json.Valid(params.Metadata) {
		return fmt.Errorf("%w: metadata must be valid JSON", domain.ErrValidation)
	}
	return nil
}

func validateTypeSpecificParams(params *ControlParams) error {
	switch params.ControlType {
	case TypeRegulatedActivity:
		if params.ProductScope == "" {
			return fmt.Errorf("%w: regulated activity decisions require product_scope", domain.ErrValidation)
		}
	case TypeOperatingModel:
		if params.ProductScope == "" {
			params.ProductScope = "platform"
		}
	case TypeLegalMemo:
		if params.Jurisdiction == "GLOBAL" {
			return fmt.Errorf("%w: legal memos require a target jurisdiction or market", domain.ErrValidation)
		}
		if params.Status == StatusApproved && params.EvidenceReference == "" && params.LegalMemoReference == "" {
			return fmt.Errorf("%w: approved legal memos require evidence_reference or legal_memo_reference", domain.ErrValidation)
		}
	case TypeProductTermApproval:
		if params.ProductScope == "" {
			return fmt.Errorf("%w: product term approvals require product_scope", domain.ErrValidation)
		}
		if params.Status == StatusRequiresDisclaimer && len(params.RequiredDisclosures) == 0 {
			return fmt.Errorf("%w: requires_disclaimer status needs required_disclosures", domain.ErrValidation)
		}
	case TypeProviderContract:
		if params.ProviderCategory == "" || params.ProviderName == "" {
			return fmt.Errorf("%w: provider contracts require provider_category and provider_name", domain.ErrValidation)
		}
		if (params.Status == StatusSigned || params.Status == StatusApproved) && params.ContractReference == "" {
			return fmt.Errorf("%w: signed provider contracts require contract_reference", domain.ErrValidation)
		}
	case TypeSafeguardingModel:
		if params.Status == StatusApproved && params.EvidenceReference == "" {
			return fmt.Errorf("%w: approved safeguarding models require evidence_reference", domain.ErrValidation)
		}
	case TypePolicyDocument:
		if params.ProductScope == "" || params.PolicyVersion == "" {
			return fmt.Errorf("%w: policy documents require product_scope and policy_version", domain.ErrValidation)
		}
		if params.Status == StatusPublished && params.EvidenceReference == "" {
			return fmt.Errorf("%w: published policies require evidence_reference", domain.ErrValidation)
		}
	case TypeJurisdictionRule:
		if params.Jurisdiction == "GLOBAL" || params.ProductScope == "" {
			return fmt.Errorf("%w: jurisdiction product rules require jurisdiction and product_scope", domain.ErrValidation)
		}
	}
	if params.ExpiresAt != nil && params.EffectiveAt != nil && params.ExpiresAt.Before(*params.EffectiveAt) {
		return fmt.Errorf("%w: expires_at cannot be before effective_at", domain.ErrValidation)
	}
	return nil
}

func validateControlType(value string) error {
	switch value {
	case TypeRegulatedActivity, TypeOperatingModel, TypeLegalMemo, TypeProductTermApproval,
		TypeProviderContract, TypeSafeguardingModel, TypePolicyDocument, TypeJurisdictionRule:
		return nil
	default:
		return fmt.Errorf("%w: invalid legal control_type", domain.ErrValidation)
	}
}

func validStatus(value string) bool {
	switch value {
	case StatusDraft, StatusInReview, StatusApproved, StatusRejected, StatusBlocked, StatusSigned,
		StatusPublished, StatusExpired, StatusDisabled, StatusTestOnly, StatusAllowed, StatusProhibited,
		StatusRequiresDisclaimer, StatusInScope, StatusOutOfScope, StatusNeedsLegalReview:
		return true
	default:
		return false
	}
}

func validRiskLevel(value string) bool {
	switch value {
	case "low", "medium", "high", "critical":
		return true
	default:
		return false
	}
}

func validOwnerTeam(value string) bool {
	switch value {
	case "legal", "compliance", "risk", "finance", "operations", "security", "product", "engineering":
		return true
	default:
		return false
	}
}

func validJurisdiction(value string) bool {
	if value == "GLOBAL" || value == "EU" || value == "EEA" {
		return true
	}
	if len(value) < 2 || len(value) > 12 {
		return false
	}
	for _, char := range value {
		if (char < 'A' || char > 'Z') && char != '-' && char != '_' {
			return false
		}
	}
	return true
}

func isOpenLegalGate(control Control) bool {
	switch control.Status {
	case StatusApproved, StatusSigned, StatusPublished, StatusAllowed, StatusOutOfScope, StatusDisabled, StatusProhibited:
		return false
	default:
		return true
	}
}

const controlColumns = `id::text, control_key, control_type, jurisdiction, product_scope,
	provider_category, provider_name, status, risk_level, owner_team, decision_summary,
	legal_memo_reference, contract_reference, policy_version, evidence_reference,
	required_disclosures, effective_at, expires_at, metadata,
	COALESCE(created_by_admin_user_id::text, ''), COALESCE(approved_by_admin_user_id::text, ''),
	approved_at, created_at, updated_at`

const controlSelect = `SELECT ` + controlColumns + ` FROM legal_market_access_controls`

type scanner interface {
	Scan(dest ...any) error
}

func scanControl(row scanner) (Control, error) {
	var control Control
	var effectiveAt, expiresAt, approvedAt sql.NullTime
	err := row.Scan(
		&control.ID,
		&control.ControlKey,
		&control.ControlType,
		&control.Jurisdiction,
		&control.ProductScope,
		&control.ProviderCategory,
		&control.ProviderName,
		&control.Status,
		&control.RiskLevel,
		&control.OwnerTeam,
		&control.DecisionSummary,
		&control.LegalMemoReference,
		&control.ContractReference,
		&control.PolicyVersion,
		&control.EvidenceReference,
		&control.RequiredDisclosures,
		&effectiveAt,
		&expiresAt,
		&control.Metadata,
		&control.CreatedByAdminUserID,
		&control.ApprovedByAdminUserID,
		&approvedAt,
		&control.CreatedAt,
		&control.UpdatedAt,
	)
	if effectiveAt.Valid {
		control.EffectiveAt = &effectiveAt.Time
	}
	if expiresAt.Valid {
		control.ExpiresAt = &expiresAt.Time
	}
	if approvedAt.Valid {
		control.ApprovedAt = &approvedAt.Time
	}
	if len(control.Metadata) == 0 {
		control.Metadata = json.RawMessage(`{}`)
	}
	return control, err
}

func normalizeLimit(limit int) int {
	if limit <= 0 || limit > 100 {
		return 50
	}
	return limit
}

func isForeignKeyViolation(err error) bool {
	var pgErr *pgconn.PgError
	return errors.As(err, &pgErr) && pgErr.Code == "23503"
}
