package ucca

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

// ============================================================================
// Financial Regulations Policy Engine
// ============================================================================
//
// Evaluates financial use-cases against DORA, MaRisk, and BAIT rules.
//
// Split into:
//   - financial_policy_types.go — all struct/type definitions and result types
//   - financial_policy.go       — engine implementation (this file)
//
// ============================================================================

// FinancialPolicyEngine evaluates intakes against financial regulations
type FinancialPolicyEngine struct {
	config *FinancialPolicyConfig
}

// NewFinancialPolicyEngine creates a new financial policy engine
func NewFinancialPolicyEngine() (*FinancialPolicyEngine, error) {
	searchPaths := []string{
		DefaultFinancialPolicyPath,
		filepath.Join(".", "policies", "financial_regulations_policy.yaml"),
		filepath.Join("..", "policies", "financial_regulations_policy.yaml"),
		filepath.Join("..", "..", "policies", "financial_regulations_policy.yaml"),
		"/app/policies/financial_regulations_policy.yaml",
	}

	var data []byte
	var err error
	for _, path := range searchPaths {
		data, err = os.ReadFile(path)
		if err == nil {
			break
		}
	}

	if err != nil {
		return nil, fmt.Errorf("failed to load financial policy from any known location: %w", err)
	}

	var config FinancialPolicyConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, fmt.Errorf("failed to parse financial policy YAML: %w", err)
	}

	return &FinancialPolicyEngine{config: &config}, nil
}

// NewFinancialPolicyEngineFromPath loads policy from a specific file path
func NewFinancialPolicyEngineFromPath(path string) (*FinancialPolicyEngine, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read financial policy file: %w", err)
	}

	var config FinancialPolicyConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, fmt.Errorf("failed to parse financial policy YAML: %w", err)
	}

	return &FinancialPolicyEngine{config: &config}, nil
}

// GetPolicyVersion returns the financial policy version
func (e *FinancialPolicyEngine) GetPolicyVersion() string { return e.config.Metadata.Version }

// IsApplicable checks if the financial policy applies to the given intake
func (e *FinancialPolicyEngine) IsApplicable(intake *UseCaseIntake) bool {
	domain := strings.ToLower(string(intake.Domain))
	for _, d := range e.config.ApplicableDomains {
		if domain == d {
			return true
		}
	}
	return false
}

// Evaluate runs financial regulation rules against the intake
func (e *FinancialPolicyEngine) Evaluate(intake *UseCaseIntake) *FinancialAssessmentResult {
	result := &FinancialAssessmentResult{
		IsApplicable:     e.IsApplicable(intake),
		Feasibility:      FeasibilityYES,
		RiskScore:        0,
		TriggeredRules:   []FinancialTriggeredRule{},
		RequiredControls: []FinancialRequiredControl{},
		IdentifiedGaps:   []FinancialIdentifiedGap{},
		StopLinesHit:     []FinancialStopLineHit{},
		EscalationLevel:  "",
		PolicyVersion:    e.config.Metadata.Version,
	}

	if !result.IsApplicable {
		return result
	}

	if intake.FinancialContext == nil {
		result.MissingContext = true
		return result
	}

	hasBlock := false
	controlSet := make(map[string]bool)
	needsEscalation := ""

	for _, rule := range e.config.Rules {
		if e.evaluateCondition(&rule.Condition, intake) {
			triggered := FinancialTriggeredRule{
				Code:        rule.ID,
				Category:    rule.Category,
				Title:       rule.Title,
				Description: rule.Description,
				Severity:    parseSeverity(rule.Severity),
				ScoreDelta:  rule.Effect.RiskAdd,
				Rationale:   rule.Rationale,
				DORARef:     rule.DORARef,
				MaRiskRef:   rule.MaRiskRef,
				BAITRef:     rule.BAITRef,
				MiFIDRef:    rule.MiFIDRef,
			}

			result.TriggeredRules = append(result.TriggeredRules, triggered)
			result.RiskScore += rule.Effect.RiskAdd

			if parseSeverity(rule.Severity) == SeverityBLOCK {
				hasBlock = true
			}

			if rule.Effect.Feasibility != "" {
				switch rule.Effect.Feasibility {
				case "NO":
					result.Feasibility = FeasibilityNO
				case "CONDITIONAL":
					if result.Feasibility != FeasibilityNO {
						result.Feasibility = FeasibilityCONDITIONAL
					}
				}
			}

			for _, ctrlID := range rule.Effect.ControlsAdd {
				if !controlSet[ctrlID] {
					controlSet[ctrlID] = true
					if ctrl, ok := e.config.Controls[ctrlID]; ok {
						result.RequiredControls = append(result.RequiredControls, FinancialRequiredControl{
							ID:             ctrl.ID,
							Title:          ctrl.Title,
							Category:       ctrl.Category,
							Description:    ctrl.Description,
							WhatToDo:       ctrl.WhatToDo,
							EvidenceNeeded: ctrl.EvidenceNeeded,
							Effort:         ctrl.Effort,
							DORARef:        ctrl.DORARef,
							MaRiskRef:      ctrl.MaRiskRef,
							BAITRef:        ctrl.BAITRef,
						})
					}
				}
			}

			if rule.Effect.Escalation {
				needsEscalation = e.determineEscalationLevel(intake)
			}
		}
	}

	for _, stopLine := range e.config.StopLines {
		if e.evaluateStopLineConditions(stopLine.When, intake) {
			result.StopLinesHit = append(result.StopLinesHit, FinancialStopLineHit{
				ID:      stopLine.ID,
				Title:   stopLine.Title,
				Message: stopLine.Message,
				Outcome: stopLine.Outcome,
			})
			result.Feasibility = FeasibilityNO
			hasBlock = true
		}
	}

	for _, gap := range e.config.Gaps {
		if e.evaluateGapConditions(gap.When, intake) {
			result.IdentifiedGaps = append(result.IdentifiedGaps, FinancialIdentifiedGap{
				ID:          gap.ID,
				Title:       gap.Title,
				Description: gap.Description,
				Severity:    parseSeverity(gap.Severity),
				Controls:    gap.Controls,
				LegalRefs:   gap.LegalRefs,
			})
			if gap.Escalation != "" && needsEscalation == "" {
				needsEscalation = gap.Escalation
			}
		}
	}

	if hasBlock {
		result.Feasibility = FeasibilityNO
	}

	result.EscalationLevel = needsEscalation
	result.Summary = e.generateSummary(result)

	return result
}

func (e *FinancialPolicyEngine) evaluateCondition(cond *FinancialConditionDef, intake *UseCaseIntake) bool {
	if len(cond.AllOf) > 0 {
		for _, subCond := range cond.AllOf {
			if !e.evaluateCondition(&subCond, intake) {
				return false
			}
		}
		return true
	}

	if len(cond.AnyOf) > 0 {
		for _, subCond := range cond.AnyOf {
			if e.evaluateCondition(&subCond, intake) {
				return true
			}
		}
		return false
	}

	if cond.Field != "" {
		return e.evaluateFieldCondition(cond.Field, cond.Operator, cond.Value, intake)
	}

	return false
}

func (e *FinancialPolicyEngine) evaluateFieldCondition(field, operator string, value interface{}, intake *UseCaseIntake) bool {
	fieldValue := e.getFieldValue(field, intake)
	if fieldValue == nil {
		return false
	}

	switch operator {
	case "equals":
		return e.compareEquals(fieldValue, value)
	case "not_equals":
		return !e.compareEquals(fieldValue, value)
	case "in":
		return e.compareIn(fieldValue, value)
	default:
		return false
	}
}

func (e *FinancialPolicyEngine) getFieldValue(field string, intake *UseCaseIntake) interface{} {
	parts := strings.Split(field, ".")
	if len(parts) == 0 {
		return nil
	}

	switch parts[0] {
	case "domain":
		return strings.ToLower(string(intake.Domain))
	case "financial_entity":
		if len(parts) < 2 || intake.FinancialContext == nil {
			return nil
		}
		return e.getFinancialEntityValue(parts[1], intake.FinancialContext)
	case "ict_service":
		if len(parts) < 2 || intake.FinancialContext == nil {
			return nil
		}
		return e.getICTServiceValue(parts[1], intake.FinancialContext)
	case "ai_application":
		if len(parts) < 2 || intake.FinancialContext == nil {
			return nil
		}
		return e.getAIApplicationValue(parts[1], intake.FinancialContext)
	case "model_usage":
		if len(parts) < 2 {
			return nil
		}
		switch parts[1] {
		case "training":
			return intake.ModelUsage.Training
		case "finetune":
			return intake.ModelUsage.Finetune
		case "rag":
			return intake.ModelUsage.RAG
		}
	}

	return nil
}

func (e *FinancialPolicyEngine) getFinancialEntityValue(field string, ctx *FinancialContext) interface{} {
	switch field {
	case "type":
		return string(ctx.FinancialEntity.Type)
	case "regulated":
		return ctx.FinancialEntity.Regulated
	case "size_category":
		return string(ctx.FinancialEntity.SizeCategory)
	}
	return nil
}

func (e *FinancialPolicyEngine) getICTServiceValue(field string, ctx *FinancialContext) interface{} {
	switch field {
	case "is_critical":
		return ctx.ICTService.IsCritical
	case "is_outsourced":
		return ctx.ICTService.IsOutsourced
	case "provider_location":
		return string(ctx.ICTService.ProviderLocation)
	case "concentration_risk":
		return ctx.ICTService.ConcentrationRisk
	}
	return nil
}

func (e *FinancialPolicyEngine) getAIApplicationValue(field string, ctx *FinancialContext) interface{} {
	switch field {
	case "affects_customer_decisions":
		return ctx.AIApplication.AffectsCustomerDecisions
	case "algorithmic_trading":
		return ctx.AIApplication.AlgorithmicTrading
	case "risk_assessment":
		return ctx.AIApplication.RiskAssessment
	case "aml_kyc":
		return ctx.AIApplication.AMLKYC
	case "model_validation_done":
		return ctx.AIApplication.ModelValidationDone
	}
	return nil
}

func (e *FinancialPolicyEngine) compareEquals(fieldValue, expected interface{}) bool {
	if bv, ok := fieldValue.(bool); ok {
		if eb, ok := expected.(bool); ok {
			return bv == eb
		}
	}
	if sv, ok := fieldValue.(string); ok {
		if es, ok := expected.(string); ok {
			return strings.EqualFold(sv, es)
		}
	}
	return false
}

func (e *FinancialPolicyEngine) compareIn(fieldValue, expected interface{}) bool {
	list, ok := expected.([]interface{})
	if !ok {
		return false
	}
	sv, ok := fieldValue.(string)
	if !ok {
		return false
	}
	for _, item := range list {
		if is, ok := item.(string); ok && strings.EqualFold(is, sv) {
			return true
		}
	}
	return false
}

func (e *FinancialPolicyEngine) evaluateStopLineConditions(conditions []string, intake *UseCaseIntake) bool {
	if intake.FinancialContext == nil {
		return false
	}
	for _, cond := range conditions {
		if !e.parseAndEvaluateSimpleCondition(cond, intake) {
			return false
		}
	}
	return len(conditions) > 0
}

func (e *FinancialPolicyEngine) evaluateGapConditions(conditions []string, intake *UseCaseIntake) bool {
	if intake.FinancialContext == nil {
		return false
	}
	for _, cond := range conditions {
		if !e.parseAndEvaluateSimpleCondition(cond, intake) {
			return false
		}
	}
	return len(conditions) > 0
}

func (e *FinancialPolicyEngine) parseAndEvaluateSimpleCondition(condition string, intake *UseCaseIntake) bool {
	if strings.Contains(condition, "==") {
		parts := strings.SplitN(condition, "==", 2)
		if len(parts) != 2 {
			return false
		}
		field := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		fieldVal := e.getFieldValue(field, intake)
		if fieldVal == nil {
			return false
		}

		if value == "true" {
			if bv, ok := fieldVal.(bool); ok {
				return bv
			}
		} else if value == "false" {
			if bv, ok := fieldVal.(bool); ok {
				return !bv
			}
		}

		if sv, ok := fieldVal.(string); ok {
			return strings.EqualFold(sv, value)
		}
	}

	return false
}

func (e *FinancialPolicyEngine) determineEscalationLevel(intake *UseCaseIntake) string {
	if intake.FinancialContext == nil {
		return ""
	}

	ctx := intake.FinancialContext

	if ctx.AIApplication.AlgorithmicTrading {
		return "E3"
	}
	if ctx.ICTService.IsCritical && ctx.ICTService.IsOutsourced {
		return "E3"
	}
	if ctx.AIApplication.RiskAssessment || ctx.AIApplication.AffectsCustomerDecisions {
		return "E2"
	}

	return "E1"
}

func (e *FinancialPolicyEngine) generateSummary(result *FinancialAssessmentResult) string {
	var parts []string

	switch result.Feasibility {
	case FeasibilityYES:
		parts = append(parts, "Der Use Case ist aus regulatorischer Sicht (DORA/MaRisk/BAIT) grundsätzlich umsetzbar.")
	case FeasibilityCONDITIONAL:
		parts = append(parts, "Der Use Case ist unter Einhaltung der Finanzregulierungen bedingt umsetzbar.")
	case FeasibilityNO:
		parts = append(parts, "Der Use Case ist ohne weitere Maßnahmen regulatorisch nicht zulässig.")
	}

	if len(result.StopLinesHit) > 0 {
		parts = append(parts, fmt.Sprintf("%d kritische Stop-Lines wurden ausgelöst.", len(result.StopLinesHit)))
	}
	if len(result.IdentifiedGaps) > 0 {
		parts = append(parts, fmt.Sprintf("%d Compliance-Lücken wurden identifiziert.", len(result.IdentifiedGaps)))
	}
	if len(result.RequiredControls) > 0 {
		parts = append(parts, fmt.Sprintf("%d regulatorische Kontrollen sind erforderlich.", len(result.RequiredControls)))
	}
	if result.EscalationLevel != "" {
		parts = append(parts, fmt.Sprintf("Eskalation auf Stufe %s empfohlen.", result.EscalationLevel))
	}

	return strings.Join(parts, " ")
}

// GetAllControls returns all controls in the financial policy
func (e *FinancialPolicyEngine) GetAllControls() map[string]FinancialControlDef {
	return e.config.Controls
}

// GetAllGaps returns all gaps in the financial policy
func (e *FinancialPolicyEngine) GetAllGaps() map[string]FinancialGapDef {
	return e.config.Gaps
}

// GetAllStopLines returns all stop lines in the financial policy
func (e *FinancialPolicyEngine) GetAllStopLines() map[string]FinancialStopLine {
	return e.config.StopLines
}

// GetApplicableDomains returns domains where financial regulations apply
func (e *FinancialPolicyEngine) GetApplicableDomains() []string {
	return e.config.ApplicableDomains
}
