package transfers

import (
	"context"
	"fmt"
	"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"
)

type Handler struct {
	repo       *Repository
	audit      *audit.Repository
	compliance interface {
		RequireClear(ctx context.Context, userID string) error
	}
	log *slog.Logger
}

type createTransferRequest struct {
	FromAccountID string `json:"from_account_id"`
	ToAccountID   string `json:"to_account_id"`
	BeneficiaryID string `json:"beneficiary_id"`
	AmountCents   int64  `json:"amount_cents"`
	Description   string `json:"description"`
	Reference     string `json:"reference"`
}

func NewHandler(repo *Repository, auditRepo *audit.Repository, compliance interface {
	RequireClear(ctx context.Context, userID string) error
}, log *slog.Logger) *Handler {
	return &Handler{repo: repo, audit: auditRepo, compliance: compliance, log: log}
}

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	if err := h.requireComplianceClear(r.Context(), claims.Subject); err != nil {
		respond.Error(w, err)
		return
	}

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

	idempotencyKey := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
	if len(idempotencyKey) > 128 {
		respond.Error(w, fmt.Errorf("%w: Idempotency-Key must be 128 characters or fewer", domain.ErrValidation))
		return
	}

	fromAccountID := strings.TrimSpace(req.FromAccountID)
	toAccountID := strings.TrimSpace(req.ToAccountID)
	if err := domain.ValidateUUID("from_account_id", fromAccountID); err != nil {
		respond.Error(w, err)
		return
	}
	beneficiaryID := strings.TrimSpace(req.BeneficiaryID)
	if (toAccountID == "") == (beneficiaryID == "") {
		respond.Error(w, fmt.Errorf("%w: provide exactly one of to_account_id or beneficiary_id", domain.ErrValidation))
		return
	}
	if toAccountID != "" {
		if err := domain.ValidateUUID("to_account_id", toAccountID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	if beneficiaryID != "" {
		if err := domain.ValidateUUID("beneficiary_id", beneficiaryID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	reference := strings.TrimSpace(req.Reference)
	if len(reference) > 140 {
		respond.Error(w, fmt.Errorf("%w: reference must be 140 characters or fewer", domain.ErrValidation))
		return
	}
	description := strings.TrimSpace(req.Description)
	if len(description) > 280 {
		respond.Error(w, fmt.Errorf("%w: description must be 280 characters or fewer", domain.ErrValidation))
		return
	}

	transfer, err := h.repo.Create(r.Context(), CreateParams{
		UserID:           claims.Subject,
		FromAccountID:    fromAccountID,
		ToAccountID:      toAccountID,
		BeneficiaryID:    beneficiaryID,
		AmountCents:      req.AmountCents,
		Description:      description,
		PaymentReference: reference,
		IdempotencyKey:   idempotencyKey,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "transfer."+transfer.Status, "transfer", transfer.ID, map[string]any{
		"amount_cents":  transfer.AmountCents,
		"currency":      transfer.Currency,
		"transfer_type": transfer.TransferType,
	})
	respond.Created(w, transfer)
}

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

	limit := 50
	if raw := r.URL.Query().Get("limit"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			limit = parsed
		}
	}

	transfers, err := h.repo.ListForUser(r.Context(), claims.Subject, limit)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	limit := 50
	if raw := r.URL.Query().Get("limit"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			limit = parsed
		}
	}

	notifications, err := h.repo.ListNotificationsForUser(r.Context(), claims.Subject, limit)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

func (h *Handler) recordAudit(r *http.Request, actorID, eventType, targetType, targetID string, metadata any) {
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &actorID,
		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)
	}
}

func (h *Handler) requireComplianceClear(ctx context.Context, userID string) error {
	if h.compliance == nil {
		return nil
	}
	return h.compliance.RequireClear(ctx, userID)
}
