package reconciliation

import (
	"encoding/json"
	"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 createSnapshotRequest struct {
	Provider          string          `json:"provider"`
	ExternalAccountID string          `json:"external_account_id"`
	ReferenceType     string          `json:"reference_type"`
	ReferenceID       string          `json:"reference_id"`
	Currency          string          `json:"currency"`
	BalanceCents      int64           `json:"balance_cents"`
	AsOf              string          `json:"as_of"`
	Source            string          `json:"source"`
	RawPayload        json.RawMessage `json:"raw_payload"`
}

type decideBreakRequest struct {
	Status         string `json:"status"`
	ResolutionNote string `json:"resolution_note"`
}

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

func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
	dashboard, err := h.repo.Dashboard(
		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) Run(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	run, err := h.repo.Run(
		r.Context(),
		claims.Subject,
		strings.TrimSpace(r.URL.Query().Get("run_type")),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.reconciliation.run.completed", "reconciliation_run", run.ID, map[string]any{
		"status":        run.Status,
		"total_checks":  run.TotalChecks,
		"matched_count": run.MatchedCount,
		"break_count":   run.BreakCount,
	})
	respond.Created(w, run)
}

func (h *Handler) RunDaily(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	snapshotDate, err := parseSnapshotDate(r.URL.Query().Get("snapshot_date"))
	if err != nil {
		respond.Error(w, err)
		return
	}
	result, err := h.repo.RunDaily(r.Context(), claims.Subject, snapshotDate)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.reconciliation.daily_run.completed", "reconciliation_run", result.Run.ID, map[string]any{
		"status":         result.Run.Status,
		"total_checks":   result.Run.TotalChecks,
		"matched_count":  result.Run.MatchedCount,
		"break_count":    result.Run.BreakCount,
		"snapshot_date":  result.SnapshotDate,
		"snapshot_count": result.SnapshotCount,
	})
	respond.Created(w, result)
}

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

	var req createSnapshotRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	var asOf *time.Time
	if strings.TrimSpace(req.AsOf) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(req.AsOf))
		if err != nil {
			respond.Error(w, domain.ErrValidation)
			return
		}
		asOf = &parsed
	}

	snapshot, err := h.repo.CreateProviderBalanceSnapshot(r.Context(), CreateSnapshotParams{
		Provider:          req.Provider,
		ExternalAccountID: req.ExternalAccountID,
		ReferenceType:     req.ReferenceType,
		ReferenceID:       req.ReferenceID,
		Currency:          req.Currency,
		BalanceCents:      req.BalanceCents,
		AsOf:              asOf,
		Source:            req.Source,
		RawPayload:        req.RawPayload,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.reconciliation.provider_snapshot.created", "provider_balance_snapshot", snapshot.ID, map[string]any{
		"provider":            snapshot.Provider,
		"external_account_id": snapshot.ExternalAccountID,
		"currency":            snapshot.Currency,
		"balance_cents":       snapshot.BalanceCents,
	})
	respond.Created(w, snapshot)
}

func (h *Handler) CreateEndOfDaySnapshots(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	snapshotDate, err := parseSnapshotDate(r.URL.Query().Get("snapshot_date"))
	if err != nil {
		respond.Error(w, err)
		return
	}
	batch, err := h.repo.CreateEndOfDaySnapshots(r.Context(), claims.Subject, snapshotDate)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.reconciliation.eod_snapshots.created", "end_of_day_balance_snapshot", batch.SnapshotDate, map[string]any{
		"snapshot_date":  batch.SnapshotDate,
		"snapshot_count": batch.SnapshotCount,
	})
	respond.Created(w, batch)
}

func (h *Handler) EndOfDaySnapshots(w http.ResponseWriter, r *http.Request) {
	snapshotDate, err := parseSnapshotDate(r.URL.Query().Get("snapshot_date"))
	if err != nil {
		respond.Error(w, err)
		return
	}
	snapshots, err := h.repo.ListEndOfDaySnapshots(
		r.Context(),
		snapshotDate,
		r.URL.Query().Get("subject_type"),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"snapshots": snapshots})
}

func (h *Handler) DecideBreak(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	breakID := r.PathValue("id")
	var req decideBreakRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}

	item, err := h.repo.DecideBreak(r.Context(), BreakDecisionParams{
		BreakID:        breakID,
		AdminUserID:    claims.Subject,
		Status:         req.Status,
		ResolutionNote: req.ResolutionNote,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.reconciliation.break.updated", "reconciliation_break", item.ID, map[string]any{
		"status":           item.Status,
		"break_type":       item.BreakType,
		"severity":         item.Severity,
		"reference_type":   item.ReferenceType,
		"reference_id":     item.ReferenceID,
		"difference_cents": item.DifferenceCents,
	})
	respond.JSON(w, http.StatusOK, item)
}

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 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 parseSnapshotDate(raw string) (time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return time.Now().UTC(), nil
	}
	parsed, err := time.Parse("2006-01-02", raw)
	if err == nil {
		return parsed, nil
	}
	parsed, err = time.Parse(time.RFC3339, raw)
	if err == nil {
		return parsed, nil
	}
	return time.Time{}, domain.ErrValidation
}
