package wallets

import (
	"fmt"
	"log/slog"
	"net/http"
	"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
	log   *slog.Logger
}

type createWalletRequest struct {
	Name string `json:"name"`
}

type addCurrencyRequest struct {
	Currency string `json:"currency"`
}

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

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

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

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

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

	name := strings.TrimSpace(req.Name)
	if name == "" {
		respond.Error(w, fmt.Errorf("%w: name is required", domain.ErrValidation))
		return
	}
	if len(name) > 80 {
		respond.Error(w, fmt.Errorf("%w: name must be 80 characters or fewer", domain.ErrValidation))
		return
	}

	wallet, err := h.repo.CreateWallet(r.Context(), CreateWalletParams{
		UserID: claims.Subject,
		Name:   name,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "wallet.created", "wallet", wallet.ID, map[string]string{"name": wallet.Name})
	respond.Created(w, wallet)
}

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

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

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

func (h *Handler) Get(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
	}

	wallet, err := h.repo.FindOwned(r.Context(), claims.Subject, walletID)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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

	currency := domain.NormalizeCurrency(req.Currency)
	if err := domain.ValidateCurrency(currency); err != nil {
		respond.Error(w, err)
		return
	}

	balance, err := h.repo.AddCurrency(r.Context(), AddCurrencyParams{
		UserID:   claims.Subject,
		WalletID: walletID,
		Currency: currency,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordAudit(r, claims.Subject, "wallet.currency_added", "wallet", walletID, map[string]string{"currency": currency})
	respond.Created(w, balance)
}

func (h *Handler) ListBalances(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
	}

	balances, err := h.repo.ListBalances(r.Context(), claims.Subject, walletID)
	if err != nil {
		respond.Error(w, err)
		return
	}

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

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)
	}
}
