package aml

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/platform/httputil"
	"github.com/niels/banking-app/backend/internal/respond"
)

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

type updateCaseRequest struct {
	Status                   string  `json:"status"`
	ResolutionNote           *string `json:"resolution_note"`
	AssignedToAdminUserID    *string `json:"assigned_to_admin_user_id"`
	SLADueAt                 string  `json:"sla_due_at"`
	RegulatoryReportRequired *bool   `json:"regulatory_report_required"`
	FalsePositive            *bool   `json:"false_positive"`
}

type addCaseNoteRequest struct {
	NoteType string `json:"note_type"`
	Body     string `json:"body"`
}

type addCaseAttachmentRequest struct {
	FileName    string `json:"file_name"`
	ContentType string `json:"content_type"`
	StorageURI  string `json:"storage_uri"`
	SHA256Hash  string `json:"sha256_hash"`
	Description string `json:"description"`
}

type falsePositiveRequest struct {
	AMLScreeningID string `json:"aml_screening_id"`
	TuningRuleID   string `json:"tuning_rule_id"`
	Outcome        string `json:"outcome"`
	Reason         string `json:"reason"`
}

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

func (h *Handler) MyScreenings(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}

	screenings, err := h.repo.ListScreenings(r.Context(), claims.Subject, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}

	respond.JSON(w, http.StatusOK, map[string]any{"screenings": screenings})
}

func (h *Handler) ListScreenings(w http.ResponseWriter, r *http.Request) {
	screenings, err := h.repo.ListScreenings(r.Context(), "", queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}

	respond.JSON(w, http.StatusOK, map[string]any{"screenings": screenings})
}

func (h *Handler) ListCases(w http.ResponseWriter, r *http.Request) {
	cases, err := h.repo.ListCases(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{"cases": cases})
}

func (h *Handler) UpdateCase(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}

	var req updateCaseRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}

	actorID := ""
	if claims, ok := middleware.CurrentClaims(r); ok {
		actorID = claims.Subject
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status != "" {
		switch status {
		case "open", "reviewing", "closed", "escalated":
		default:
			respond.Error(w, fmt.Errorf("%w: status must be open, reviewing, closed or escalated", domain.ErrValidation))
			return
		}
	}
	if req.AssignedToAdminUserID != nil && strings.TrimSpace(*req.AssignedToAdminUserID) != "" {
		if err := domain.ValidateUUID("assigned_to_admin_user_id", strings.TrimSpace(*req.AssignedToAdminUserID)); err != nil {
			respond.Error(w, err)
			return
		}
	}
	var slaDueAt *time.Time
	if strings.TrimSpace(req.SLADueAt) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(req.SLADueAt))
		if err != nil {
			respond.Error(w, fmt.Errorf("%w: sla_due_at must be RFC3339", domain.ErrValidation))
			return
		}
		slaDueAt = &parsed
	}
	historyNote := "case updated"
	if req.ResolutionNote != nil && strings.TrimSpace(*req.ResolutionNote) != "" {
		trimmed := strings.TrimSpace(*req.ResolutionNote)
		req.ResolutionNote = &trimmed
		historyNote = trimmed
	}

	amlCase, err := h.repo.UpdateCase(r.Context(), UpdateCaseParams{
		CaseID:                   caseID,
		ActorAdminUserID:         actorID,
		Status:                   status,
		ResolutionNote:           req.ResolutionNote,
		AssignedToAdminUserID:    trimStringPtr(req.AssignedToAdminUserID),
		SLADueAt:                 slaDueAt,
		RegulatoryReportRequired: req.RegulatoryReportRequired,
		FalsePositive:            req.FalsePositive,
		HistoryNote:              historyNote,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "aml_case.updated", "aml_case", amlCase.ID, map[string]string{"status": amlCase.Status})

	respond.JSON(w, http.StatusOK, amlCase)
}

func (h *Handler) CaseNotes(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	notes, err := h.repo.ListCaseNotes(r.Context(), caseID, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"case_notes": notes})
}

func (h *Handler) AddCaseNote(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	var req addCaseNoteRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	noteType := strings.ToLower(strings.TrimSpace(req.NoteType))
	if noteType == "" {
		noteType = "internal"
	}
	body := strings.TrimSpace(req.Body)
	if err := ValidateCaseNote(noteType, body); err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	note, err := h.repo.AddCaseNote(r.Context(), caseID, actorID, noteType, body)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "aml_case.note_added", "aml_case", caseID, map[string]string{"note_type": note.NoteType})
	respond.Created(w, note)
}

func (h *Handler) CaseAttachments(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	attachments, err := h.repo.ListCaseAttachments(r.Context(), caseID, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"case_attachments": attachments})
}

func (h *Handler) AddCaseAttachment(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	var req addCaseAttachmentRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	fileName := strings.TrimSpace(req.FileName)
	if fileName == "" {
		respond.Error(w, fmt.Errorf("%w: file_name is required", domain.ErrValidation))
		return
	}
	contentType := strings.TrimSpace(req.ContentType)
	if contentType == "" {
		contentType = "application/octet-stream"
	}
	actorID := currentActorID(r)
	attachment, err := h.repo.AddCaseAttachment(r.Context(), CaseAttachmentParams{
		CaseID:                caseID,
		UploadedByAdminUserID: actorID,
		FileName:              fileName,
		ContentType:           contentType,
		StorageURI:            strings.TrimSpace(req.StorageURI),
		SHA256Hash:            strings.TrimSpace(req.SHA256Hash),
		Description:           strings.TrimSpace(req.Description),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "aml_case.attachment_added", "aml_case", caseID, map[string]string{"file_name": attachment.FileName})
	respond.Created(w, attachment)
}

func (h *Handler) CaseHistory(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	history, err := h.repo.ListCaseHistory(r.Context(), caseID, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"case_history": history})
}

func (h *Handler) RecordFalsePositive(w http.ResponseWriter, r *http.Request) {
	caseID := r.PathValue("id")
	if err := domain.ValidateUUID("id", caseID); err != nil {
		respond.Error(w, err)
		return
	}
	var req falsePositiveRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	outcome := strings.ToLower(strings.TrimSpace(req.Outcome))
	switch outcome {
	case "confirmed_false_positive", "true_positive", "inconclusive":
	default:
		respond.Error(w, fmt.Errorf("%w: outcome must be confirmed_false_positive, true_positive or inconclusive", domain.ErrValidation))
		return
	}
	if strings.TrimSpace(req.AMLScreeningID) != "" {
		if err := domain.ValidateUUID("aml_screening_id", strings.TrimSpace(req.AMLScreeningID)); err != nil {
			respond.Error(w, err)
			return
		}
	}
	if strings.TrimSpace(req.TuningRuleID) != "" {
		if err := domain.ValidateUUID("tuning_rule_id", strings.TrimSpace(req.TuningRuleID)); err != nil {
			respond.Error(w, err)
			return
		}
	}
	actorID := currentActorID(r)
	feedback, err := h.repo.RecordFalsePositiveFeedback(
		r.Context(),
		caseID,
		strings.TrimSpace(req.AMLScreeningID),
		strings.TrimSpace(req.TuningRuleID),
		actorID,
		outcome,
		strings.TrimSpace(req.Reason),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "aml_case.false_positive_feedback", "aml_case", caseID, map[string]string{"outcome": feedback.Outcome})
	respond.Created(w, feedback)
}

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 trimStringPtr(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	return &trimmed
}

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.Error("audit record failed", "error", err)
	}
}
