package admin

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

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

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

type walletAdjustmentRequest struct {
	AccountID   string `json:"account_id"`
	Direction   string `json:"direction"`
	AmountCents int64  `json:"amount_cents"`
	Currency    string `json:"currency"`
	Reason      string `json:"reason"`
}

type paymentHoldRequest struct {
	ReasonCode string `json:"reason_code"`
	Note       string `json:"note"`
}

type paymentReviewDecisionRequest struct {
	DecisionNote string `json:"decision_note"`
}

type routingCodeRequirementRequest struct {
	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"`
	ContractVersion   string         `json:"contract_version"`
	ContractSignedAt  string         `json:"contract_signed_at"`
	EffectiveFrom     string         `json:"effective_from"`
	EffectiveTo       string         `json:"effective_to"`
	ApprovalStatus    string         `json:"approval_status"`
	Metadata          map[string]any `json:"metadata"`
}

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

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

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

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

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

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

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

func (h *Handler) SEPASettlements(w http.ResponseWriter, r *http.Request) {
	dashboard, err := h.repo.SEPASettlementDashboard(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("status")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

func (h *Handler) SEPASettlementEvents(w http.ResponseWriter, r *http.Request) {
	transferID := strings.TrimSpace(r.URL.Query().Get("transfer_id"))
	if transferID != "" {
		if err := domain.ValidateUUID("transfer_id", transferID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	events, err := h.repo.ListSEPASettlementEvents(
		r.Context(),
		transferID,
		strings.TrimSpace(r.URL.Query().Get("event_type")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

func (h *Handler) RoutingCodeRequirements(w http.ResponseWriter, r *http.Request) {
	requirements, err := h.repo.ListRoutingCodeRequirements(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("provider")),
		strings.TrimSpace(r.URL.Query().Get("country")),
		strings.TrimSpace(r.URL.Query().Get("network")),
		strings.TrimSpace(r.URL.Query().Get("rule_source")),
		strings.TrimSpace(r.URL.Query().Get("approval_status")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

func (h *Handler) CreateRoutingCodeRequirement(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	params, err := decodeRoutingCodeRequirementRequest(r)
	if err != nil {
		respond.Error(w, err)
		return
	}
	requirement, err := h.repo.CreateRoutingCodeRequirement(r.Context(), claims.Subject, params)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordRoutingCodeRequirementAudit(r, claims.Subject, "admin.routing_code_requirement.created", requirement)
	respond.Created(w, requirement)
}

func (h *Handler) UpdateRoutingCodeRequirement(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	requirementID := r.PathValue("id")
	if err := domain.ValidateUUID("id", requirementID); err != nil {
		respond.Error(w, err)
		return
	}
	params, err := decodeRoutingCodeRequirementRequest(r)
	if err != nil {
		respond.Error(w, err)
		return
	}
	requirement, err := h.repo.UpdateRoutingCodeRequirement(r.Context(), requirementID, claims.Subject, params)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordRoutingCodeRequirementAudit(r, claims.Subject, "admin.routing_code_requirement.updated", requirement)
	respond.JSON(w, http.StatusOK, requirement)
}

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

func (h *Handler) HoldPaymentForReview(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	transferID := r.PathValue("id")
	if err := domain.ValidateUUID("id", transferID); err != nil {
		respond.Error(w, err)
		return
	}
	var req paymentHoldRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	review, err := h.repo.HoldPaymentForReview(r.Context(), PaymentHoldParams{
		TransferID:   transferID,
		ActorAdminID: claims.Subject,
		ReasonCode:   req.ReasonCode,
		Note:         req.Note,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordPaymentReviewAudit(r, claims.Subject, "admin.payment_review.held", review)
	respond.JSON(w, http.StatusOK, review)
}

func (h *Handler) ReleasePaymentReview(w http.ResponseWriter, r *http.Request) {
	h.decidePaymentReview(w, r, "release")
}

func (h *Handler) RejectPaymentReview(w http.ResponseWriter, r *http.Request) {
	h.decidePaymentReview(w, r, "reject")
}

func (h *Handler) decidePaymentReview(w http.ResponseWriter, r *http.Request, action string) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	reviewID := r.PathValue("id")
	if err := domain.ValidateUUID("id", reviewID); err != nil {
		respond.Error(w, err)
		return
	}
	var req paymentReviewDecisionRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	review, err := h.repo.DecidePaymentReview(r.Context(), PaymentReviewDecisionParams{
		ReviewID:     reviewID,
		ActorAdminID: claims.Subject,
		Action:       action,
		DecisionNote: req.DecisionNote,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	eventTypes := map[string]string{
		"release": "admin.payment_review.released",
		"reject":  "admin.payment_review.rejected",
	}
	h.recordPaymentReviewAudit(r, claims.Subject, eventTypes[action], review)
	respond.JSON(w, http.StatusOK, review)
}

func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
	users, err := h.repo.ListUsers(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("query")),
		strings.ToLower(strings.TrimSpace(r.URL.Query().Get("role"))),
		strings.ToLower(strings.TrimSpace(r.URL.Query().Get("kyc_status"))),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	detail, err := h.repo.UserDetail(r.Context(), userID)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	detail, err := h.repo.AccountDetail(r.Context(), accountID)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	entries, err := h.repo.LedgerEntriesForUser(r.Context(), userID, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	entries, err := h.repo.LedgerEntriesForAccount(r.Context(), accountID, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}

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

func (h *Handler) CreateWalletAdjustmentRequest(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	walletID := r.PathValue("id")
	if err := domain.ValidateUUID("id", walletID); err != nil {
		respond.Error(w, err)
		return
	}

	var req walletAdjustmentRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	accountID := strings.TrimSpace(req.AccountID)
	if accountID != "" {
		if err := domain.ValidateUUID("account_id", accountID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	params, err := NormalizeWalletAdjustment(
		req.Direction,
		req.Currency,
		req.Reason,
		r.Header.Get("Idempotency-Key"),
		req.AmountCents,
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	params.AdminUserID = claims.Subject
	params.WalletID = walletID
	params.AccountID = accountID

	request, err := h.repo.CreateWalletAdjustmentRequest(r.Context(), params)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordRequestAudit(r, claims.Subject, "admin.wallet_balance_adjustment_request.created", request)
	respond.Created(w, request)
}

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

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

func (h *Handler) ApproveWalletAdjustmentRequest(w http.ResponseWriter, r *http.Request) {
	h.decideWalletAdjustmentRequest(w, r, "approve")
}

func (h *Handler) RejectWalletAdjustmentRequest(w http.ResponseWriter, r *http.Request) {
	h.decideWalletAdjustmentRequest(w, r, "reject")
}

func (h *Handler) CancelWalletAdjustmentRequest(w http.ResponseWriter, r *http.Request) {
	h.decideWalletAdjustmentRequest(w, r, "cancel")
}

func (h *Handler) decideWalletAdjustmentRequest(w http.ResponseWriter, r *http.Request, action string) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	requestID := r.PathValue("id")
	if err := domain.ValidateUUID("id", requestID); err != nil {
		respond.Error(w, err)
		return
	}
	var req struct {
		DecisionNote string `json:"decision_note"`
	}
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	request, err := h.repo.DecideWalletAdjustmentRequest(r.Context(), WalletAdjustmentDecisionParams{
		RequestID:    requestID,
		ActorAdminID: claims.Subject,
		Action:       action,
		DecisionNote: req.DecisionNote,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	eventTypes := map[string]string{
		"approve": "admin.wallet_balance_adjustment_request.approved",
		"reject":  "admin.wallet_balance_adjustment_request.rejected",
		"cancel":  "admin.wallet_balance_adjustment_request.canceled",
	}
	h.recordRequestAudit(r, claims.Subject, eventTypes[action], request)
	respond.JSON(w, http.StatusOK, request)
}

func (h *Handler) recordRequestAudit(r *http.Request, adminUserID, eventType string, request domain.WalletBalanceAdjustmentRequest) {
	if h.audit == nil {
		return
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &adminUserID,
		EventType:   eventType,
		TargetType:  "wallet_balance_adjustment_request",
		TargetID:    request.ID,
		Metadata: map[string]any{
			"target_user_id": request.TargetUserID,
			"wallet_id":      request.WalletID,
			"account_id":     request.AccountID,
			"direction":      request.Direction,
			"amount_cents":   request.AmountCents,
			"currency":       request.Currency,
			"status":         request.Status,
			"adjustment_id":  request.AdjustmentID,
		},
		RemoteIP:  httputil.RemoteIP(r),
		UserAgent: r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}

func (h *Handler) recordPaymentReviewAudit(r *http.Request, adminUserID, eventType string, review PaymentReviewCase) {
	if h.audit == nil {
		return
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &adminUserID,
		EventType:   eventType,
		TargetType:  "payment_review_case",
		TargetID:    review.ID,
		Metadata: map[string]any{
			"transfer_id":     review.TransferID,
			"user_id":         review.UserID,
			"amount_cents":    review.AmountCents,
			"currency":        review.Currency,
			"status":          review.Status,
			"transfer_status": review.TransferStatus,
			"reason_code":     review.ReasonCode,
		},
		RemoteIP:  httputil.RemoteIP(r),
		UserAgent: r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}

func (h *Handler) recordRoutingCodeRequirementAudit(r *http.Request, adminUserID, eventType string, requirement routingcodes.Requirement) {
	if h.audit == nil {
		return
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &adminUserID,
		EventType:   eventType,
		TargetType:  "routing_code_requirement",
		TargetID:    requirement.ID,
		Metadata: map[string]any{
			"provider":           requirement.Provider,
			"country":            requirement.Country,
			"network":            requirement.Network,
			"currency":           requirement.Currency,
			"code_type":          requirement.CodeType,
			"rule_source":        requirement.RuleSource,
			"approval_status":    requirement.ApprovalStatus,
			"contract_reference": requirement.ContractReference,
			"contract_version":   requirement.ContractVersion,
		},
		RemoteIP:  httputil.RemoteIP(r),
		UserAgent: r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}

func decodeRoutingCodeRequirementRequest(r *http.Request) (routingcodes.RequirementParams, error) {
	var req routingCodeRequirementRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		return routingcodes.RequirementParams{}, err
	}
	required := true
	if req.Required != nil {
		required = *req.Required
	}
	active := true
	if req.Active != nil {
		active = *req.Active
	}
	metadata, err := metadataBytes(req.Metadata)
	if err != nil {
		return routingcodes.RequirementParams{}, err
	}
	return routingcodes.RequirementParams{
		Provider:          req.Provider,
		Country:           req.Country,
		Network:           req.Network,
		Currency:          req.Currency,
		CodeType:          req.CodeType,
		Required:          required,
		Active:            active,
		Priority:          req.Priority,
		Description:       req.Description,
		RuleSource:        req.RuleSource,
		ContractReference: req.ContractReference,
		ContractVersion:   req.ContractVersion,
		ContractSignedAt:  req.ContractSignedAt,
		EffectiveFrom:     req.EffectiveFrom,
		EffectiveTo:       req.EffectiveTo,
		ApprovalStatus:    req.ApprovalStatus,
		Metadata:          metadata,
	}, 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
}
