package fx

import (
	"context"
	"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
	compliance interface {
		RequireClear(ctx context.Context, userID string) error
	}
	log *slog.Logger
}

type createQuoteRequest struct {
	FromAccountID   string `json:"from_account_id"`
	ToAccountID     string `json:"to_account_id"`
	FromAmountCents int64  `json:"from_amount_cents"`
}

type convertQuoteRequest struct {
	QuoteID string `json:"quote_id"`
}

type createRateRequest struct {
	BaseCurrency    string `json:"base_currency"`
	QuoteCurrency   string `json:"quote_currency"`
	RateMicros      int64  `json:"rate_micros"`
	SpreadBps       int    `json:"spread_bps"`
	Source          string `json:"source"`
	Provider        string `json:"provider"`
	SourceReference string `json:"source_reference"`
	SourceTimestamp string `json:"source_timestamp"`
	ValidFrom       string `json:"valid_from"`
	StaleAfter      string `json:"stale_after"`
	IsFallback      bool   `json:"is_fallback"`
}

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

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) ListRates(w http.ResponseWriter, r *http.Request) {
	rates, err := h.repo.ListRates(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("base")),
		strings.TrimSpace(r.URL.Query().Get("quote")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"exchange_rates": rates})
}

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

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

	quote, err := h.repo.CreateQuote(r.Context(), CreateQuoteParams{
		UserID:          claims.Subject,
		FromAccountID:   strings.TrimSpace(req.FromAccountID),
		ToAccountID:     strings.TrimSpace(req.ToAccountID),
		FromAmountCents: req.FromAmountCents,
		IdempotencyKey:  strings.TrimSpace(r.Header.Get("Idempotency-Key")),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "fx.quote.created", "fx_quote", quote.ID, map[string]any{
		"from_account_id":   quote.FromAccountID,
		"to_account_id":     quote.ToAccountID,
		"from_currency":     quote.FromCurrency,
		"to_currency":       quote.ToCurrency,
		"from_amount_cents": quote.FromAmountCents,
		"to_amount_cents":   quote.ToAmountCents,
		"rate_micros":       quote.RateMicros,
		"expires_at":        quote.ExpiresAt,
	})
	respond.Created(w, quote)
}

func (h *Handler) ConvertQuote(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 convertQuoteRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}

	conversion, err := h.repo.ConvertQuote(r.Context(), ConvertQuoteParams{
		UserID:  claims.Subject,
		QuoteID: strings.TrimSpace(req.QuoteID),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "fx.conversion.completed", "fx_conversion", conversion.ID, map[string]any{
		"quote_id":          conversion.QuoteID,
		"from_account_id":   conversion.FromAccountID,
		"to_account_id":     conversion.ToAccountID,
		"from_currency":     conversion.FromCurrency,
		"to_currency":       conversion.ToCurrency,
		"from_amount_cents": conversion.FromAmountCents,
		"to_amount_cents":   conversion.ToAmountCents,
	})
	respond.Created(w, conversion)
}

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

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

	validFrom, err := parseOptionalRFC3339("valid_from", req.ValidFrom)
	if err != nil {
		respond.Error(w, err)
		return
	}
	sourceTimestamp, err := parseOptionalRFC3339("source_timestamp", req.SourceTimestamp)
	if err != nil {
		respond.Error(w, err)
		return
	}
	staleAfter, err := parseOptionalRFC3339("stale_after", req.StaleAfter)
	if err != nil {
		respond.Error(w, err)
		return
	}

	request, err := h.repo.CreateRateChangeRequest(r.Context(), CreateRateParams{
		BaseCurrency:     req.BaseCurrency,
		QuoteCurrency:    req.QuoteCurrency,
		RateMicros:       req.RateMicros,
		SpreadBps:        req.SpreadBps,
		Source:           req.Source,
		Provider:         req.Provider,
		SourceReference:  req.SourceReference,
		SourceTimestamp:  sourceTimestamp,
		ValidFrom:        validFrom,
		StaleAfter:       staleAfter,
		IsFallback:       req.IsFallback,
		RequesterAdminID: claims.Subject,
		IdempotencyKey:   strings.TrimSpace(r.Header.Get("Idempotency-Key")),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "admin.fx_rate_change_request.created", "fx_rate_change_request", request.ID, map[string]any{
		"base_currency":    request.BaseCurrency,
		"quote_currency":   request.QuoteCurrency,
		"rate_micros":      request.RateMicros,
		"spread_bps":       request.SpreadBps,
		"source":           request.Source,
		"provider":         request.Provider,
		"source_reference": request.SourceReference,
		"valid_from":       request.ValidFrom,
		"stale_after":      request.StaleAfter,
		"is_fallback":      request.IsFallback,
	})
	respond.Created(w, request)
}

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

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

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

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

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

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

	request, err := h.repo.DecideRateChangeRequest(r.Context(), RateDecisionParams{
		RequestID:    strings.TrimSpace(r.PathValue("id")),
		ActorAdminID: claims.Subject,
		Action:       action,
		DecisionNote: req.DecisionNote,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	eventType := map[string]string{
		"approve": "admin.fx_rate_change_request.approved",
		"reject":  "admin.fx_rate_change_request.rejected",
		"cancel":  "admin.fx_rate_change_request.canceled",
	}[action]
	h.recordAudit(r, claims.Subject, eventType, "fx_rate_change_request", request.ID, map[string]any{
		"status":           request.Status,
		"exchange_rate_id": request.ExchangeRateID,
		"base_currency":    request.BaseCurrency,
		"quote_currency":   request.QuoteCurrency,
		"rate_micros":      request.RateMicros,
		"spread_bps":       request.SpreadBps,
	})
	respond.JSON(w, http.StatusOK, request)
}

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

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

func (h *Handler) AdminAuditExport(w http.ResponseWriter, r *http.Request) {
	from, err := parseOptionalRFC3339("from", r.URL.Query().Get("from"))
	if err != nil {
		respond.Error(w, err)
		return
	}
	to, err := parseOptionalRFC3339("to", r.URL.Query().Get("to"))
	if err != nil {
		respond.Error(w, err)
		return
	}
	export, err := h.repo.AuditExport(r.Context(), from, to, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, export)
}

func (h *Handler) recordAudit(r *http.Request, actorID, eventType, targetType, targetID string, metadata any) {
	if h.audit == nil {
		return
	}
	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 != 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)
}

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 parseOptionalRFC3339(field, raw string) (*time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, raw)
	if err != nil {
		return nil, fmt.Errorf("%w: %s must be RFC3339", domain.ErrValidation, field)
	}
	parsed = parsed.UTC()
	return &parsed, nil
}
