package compliance

import (
	"fmt"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/niels/banking-app/backend/internal/audit"
	"github.com/niels/banking-app/backend/internal/domain"
	"github.com/niels/banking-app/backend/internal/httpapi/middleware"
	"github.com/niels/banking-app/backend/internal/integrations/sanctions"
	"github.com/niels/banking-app/backend/internal/platform/httputil"
	"github.com/niels/banking-app/backend/internal/respond"
)

type Handler struct {
	repo      *Repository
	audit     *audit.Repository
	sanctions sanctions.Provider
	log       *slog.Logger
}

type monitoringRunRequest struct {
	UserID string `json:"user_id"`
}

type createEDDReviewRequest struct {
	UserID           string   `json:"user_id"`
	AMLCaseID        string   `json:"aml_case_id"`
	Reason           string   `json:"reason"`
	RequiredEvidence []string `json:"required_evidence"`
	DueAt            string   `json:"due_at"`
}

type decideEDDReviewRequest struct {
	Status       string `json:"status"`
	DecisionNote string `json:"decision_note"`
}

type createRegulatoryReportRequest struct {
	AMLCaseID    string `json:"aml_case_id"`
	ReportType   string `json:"report_type"`
	Jurisdiction string `json:"jurisdiction"`
	Narrative    string `json:"narrative"`
}

type updateRegulatoryReportRequest struct {
	Status          string `json:"status"`
	FilingReference string `json:"filing_reference"`
	Narrative       string `json:"narrative"`
}

type tuningRuleRequest struct {
	Signal          string `json:"signal"`
	Description     string `json:"description"`
	Weight          int    `json:"weight"`
	ReviewThreshold int    `json:"review_threshold"`
	BlockThreshold  int    `json:"block_threshold"`
	Enabled         *bool  `json:"enabled"`
}

func NewHandler(repo *Repository, auditRepo *audit.Repository, sanctionsProvider sanctions.Provider, log *slog.Logger) *Handler {
	return &Handler{repo: repo, audit: auditRepo, sanctions: sanctionsProvider, log: log}
}

func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
	dashboard, err := h.repo.Dashboard(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, dashboard)
}

func (h *Handler) CustomerRiskProfiles(w http.ResponseWriter, r *http.Request) {
	profiles, err := h.repo.ListCustomerRisk(r.Context(), strings.TrimSpace(r.URL.Query().Get("tier")), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"customer_risk_profiles": profiles})
}

func (h *Handler) RecalculateCustomerRisk(w http.ResponseWriter, r *http.Request) {
	userID := r.PathValue("id")
	if err := domain.ValidateUUID("id", userID); err != nil {
		respond.Error(w, err)
		return
	}
	profile, err := h.repo.RecalculateCustomerRisk(r.Context(), userID)
	if err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	h.recordAudit(r, actorID, "compliance.customer_risk_recalculated", "user", userID, map[string]any{
		"risk_score": profile.RiskScore,
		"risk_tier":  profile.RiskTier,
	})
	respond.JSON(w, http.StatusOK, profile)
}

func (h *Handler) RunMonitoring(w http.ResponseWriter, r *http.Request) {
	var req monitoringRunRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	userID := strings.TrimSpace(req.UserID)
	if userID != "" {
		if err := domain.ValidateUUID("user_id", userID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	actorID := currentActorID(r)
	run, err := h.repo.RunOngoingMonitoring(r.Context(), userID, actorID, h.sanctions)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "compliance.monitoring_run.completed", "compliance_monitoring_run", run.ID, map[string]any{
		"user_id":        run.UserID,
		"screened_count": run.ScreenedCount,
		"hit_count":      run.HitCount,
		"review_count":   run.ReviewCount,
		"error_count":    run.ErrorCount,
	})
	respond.Created(w, run)
}

func (h *Handler) EDDReviews(w http.ResponseWriter, r *http.Request) {
	reviews, err := h.repo.ListEDDReviews(r.Context(), strings.TrimSpace(r.URL.Query().Get("status")), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"edd_reviews": reviews})
}

func (h *Handler) CreateEDDReview(w http.ResponseWriter, r *http.Request) {
	var req createEDDReviewRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	userID := strings.TrimSpace(req.UserID)
	if err := domain.ValidateUUID("user_id", userID); err != nil {
		respond.Error(w, err)
		return
	}
	amlCaseID := strings.TrimSpace(req.AMLCaseID)
	if amlCaseID != "" {
		if err := domain.ValidateUUID("aml_case_id", amlCaseID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	reason := strings.TrimSpace(req.Reason)
	if reason == "" {
		respond.Error(w, fmt.Errorf("%w: reason is required", domain.ErrValidation))
		return
	}
	var dueAt *time.Time
	if strings.TrimSpace(req.DueAt) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(req.DueAt))
		if err != nil {
			respond.Error(w, fmt.Errorf("%w: due_at must be RFC3339", domain.ErrValidation))
			return
		}
		dueAt = &parsed
	}
	actorID := currentActorID(r)
	review, err := h.repo.CreateEDDReview(r.Context(), EDDReviewParams{
		UserID:                 userID,
		AMLCaseID:              amlCaseID,
		Reason:                 reason,
		RequiredEvidence:       req.RequiredEvidence,
		RequestedByAdminUserID: actorID,
		DueAt:                  dueAt,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "compliance.edd_review.created", "edd_review", review.ID, map[string]string{"user_id": userID})
	respond.Created(w, review)
}

func (h *Handler) DecideEDDReview(w http.ResponseWriter, r *http.Request) {
	reviewID := r.PathValue("id")
	if err := domain.ValidateUUID("id", reviewID); err != nil {
		respond.Error(w, err)
		return
	}
	var req decideEDDReviewRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	switch status {
	case "requested", "in_progress", "approved", "rejected", "closed":
	default:
		respond.Error(w, fmt.Errorf("%w: status must be requested, in_progress, approved, rejected or closed", domain.ErrValidation))
		return
	}
	actorID := currentActorID(r)
	review, err := h.repo.DecideEDDReview(r.Context(), reviewID, status, strings.TrimSpace(req.DecisionNote), actorID)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "compliance.edd_review.updated", "edd_review", review.ID, map[string]string{"status": review.Status})
	respond.JSON(w, http.StatusOK, review)
}

func (h *Handler) RegulatoryReports(w http.ResponseWriter, r *http.Request) {
	reports, err := h.repo.ListRegulatoryReports(r.Context(), strings.TrimSpace(r.URL.Query().Get("status")), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"regulatory_reports": reports})
}

func (h *Handler) CreateRegulatoryReport(w http.ResponseWriter, r *http.Request) {
	var req createRegulatoryReportRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	caseID := strings.TrimSpace(req.AMLCaseID)
	if err := domain.ValidateUUID("aml_case_id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	reportType := strings.ToLower(strings.TrimSpace(req.ReportType))
	switch reportType {
	case "sar", "str", "ctr", "other":
	default:
		respond.Error(w, fmt.Errorf("%w: report_type must be sar, str, ctr or other", domain.ErrValidation))
		return
	}
	actorID := currentActorID(r)
	report, err := h.repo.CreateRegulatoryReport(r.Context(), RegulatoryReportParams{
		AMLCaseID:    caseID,
		ReportType:   reportType,
		Jurisdiction: strings.ToUpper(strings.TrimSpace(req.Jurisdiction)),
		Narrative:    strings.TrimSpace(req.Narrative),
		AdminUserID:  actorID,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "compliance.regulatory_report.created", "regulatory_report", report.ID, map[string]string{"aml_case_id": caseID})
	respond.Created(w, report)
}

func (h *Handler) UpdateRegulatoryReport(w http.ResponseWriter, r *http.Request) {
	reportID := r.PathValue("id")
	if err := domain.ValidateUUID("id", reportID); err != nil {
		respond.Error(w, err)
		return
	}
	var req updateRegulatoryReportRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status != "" {
		switch status {
		case "draft", "in_review", "submitted", "rejected", "withdrawn":
		default:
			respond.Error(w, fmt.Errorf("%w: status must be draft, in_review, submitted, rejected or withdrawn", domain.ErrValidation))
			return
		}
	}
	actorID := currentActorID(r)
	report, err := h.repo.UpdateRegulatoryReport(r.Context(), reportID, status, strings.TrimSpace(req.FilingReference), strings.TrimSpace(req.Narrative), actorID)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "compliance.regulatory_report.updated", "regulatory_report", report.ID, map[string]string{"status": report.Status})
	respond.JSON(w, http.StatusOK, report)
}

func (h *Handler) TuningRules(w http.ResponseWriter, r *http.Request) {
	rules, err := h.repo.ListTuningRules(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"tuning_rules": rules})
}

func (h *Handler) CreateTuningRule(w http.ResponseWriter, r *http.Request) {
	rule, err := h.decodeTuningRule(r, "")
	if err != nil {
		respond.Error(w, err)
		return
	}
	created, err := h.repo.UpsertTuningRule(r.Context(), rule)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, rule.AdminUserID, "compliance.tuning_rule.upserted", "compliance_tuning_rule", created.ID, map[string]string{"signal": created.Signal})
	respond.Created(w, created)
}

func (h *Handler) UpdateTuningRule(w http.ResponseWriter, r *http.Request) {
	ruleID := r.PathValue("id")
	if err := domain.ValidateUUID("id", ruleID); err != nil {
		respond.Error(w, err)
		return
	}
	rule, err := h.decodeTuningRule(r, ruleID)
	if err != nil {
		respond.Error(w, err)
		return
	}
	updated, err := h.repo.UpsertTuningRule(r.Context(), rule)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, rule.AdminUserID, "compliance.tuning_rule.updated", "compliance_tuning_rule", updated.ID, map[string]string{"signal": updated.Signal})
	respond.JSON(w, http.StatusOK, updated)
}

func (h *Handler) decodeTuningRule(r *http.Request, ruleID string) (TuningRuleParams, error) {
	var req tuningRuleRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		return TuningRuleParams{}, err
	}
	signal := strings.ToLower(strings.TrimSpace(req.Signal))
	if signal == "" {
		return TuningRuleParams{}, fmt.Errorf("%w: signal is required", domain.ErrValidation)
	}
	if req.ReviewThreshold <= 0 {
		req.ReviewThreshold = 70
	}
	if req.BlockThreshold <= 0 {
		req.BlockThreshold = 90
	}
	if req.ReviewThreshold > req.BlockThreshold {
		return TuningRuleParams{}, fmt.Errorf("%w: review_threshold cannot exceed block_threshold", domain.ErrValidation)
	}
	enabled := true
	if req.Enabled != nil {
		enabled = *req.Enabled
	}
	return TuningRuleParams{
		ID:              ruleID,
		Signal:          signal,
		Description:     strings.TrimSpace(req.Description),
		Weight:          req.Weight,
		ReviewThreshold: req.ReviewThreshold,
		BlockThreshold:  req.BlockThreshold,
		Enabled:         enabled,
		AdminUserID:     currentActorID(r),
	}, nil
}

func queryLimit(r *http.Request) int {
	limit := 50
	if raw := r.URL.Query().Get("limit"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			limit = parsed
		}
	}
	return limit
}

func currentActorID(r *http.Request) string {
	if claims, ok := middleware.CurrentClaims(r); ok {
		return claims.Subject
	}
	return ""
}

func (h *Handler) recordAudit(r *http.Request, actorID, eventType, targetType, targetID string, metadata any) {
	var actor *string
	if actorID != "" {
		actor = &actorID
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: actor,
		EventType:   eventType,
		TargetType:  targetType,
		TargetID:    targetID,
		Metadata:    metadata,
		RemoteIP:    httputil.RemoteIP(r),
		UserAgent:   r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}
