package httpapi

import (
	"encoding/json"
	"errors"
	"net"
	"net/http"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type walletBalanceRequest struct {
	Currency       string         `json:"currency" example:"USD"`
	Balance        json.Number    `json:"balance" swaggertype:"number" example:"100"`
	Reason         string         `json:"reason" example:"manual recharge"`
	IdempotencyKey string         `json:"idempotencyKey" example:"wallet-set-20260514-001"`
	Metadata       map[string]any `json:"metadata"`
}

type walletRechargeRequest struct {
	Currency       string         `json:"currency" example:"resource"`
	Amount         json.Number    `json:"amount" swaggertype:"number" example:"100"`
	Reason         string         `json:"reason" example:"manual recharge"`
	IdempotencyKey string         `json:"idempotencyKey" example:"wallet-recharge-20260514-001"`
	Metadata       map[string]any `json:"metadata"`
}

// setUserWalletBalance godoc
// @Summary 设置用户钱包余额
// @Description 管理端把指定用户钱包余额调整到目标值，并记录审计日志；balance 不允许为负数。
// @Tags billing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userID path string true "用户 ID"
// @Param input body walletBalanceRequest true "钱包余额设置请求"
// @Success 200 {object} WalletAdjustmentResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/{userID}/wallet [patch]
func (s *Server) setUserWalletBalance(w http.ResponseWriter, r *http.Request) {
	actor, _ := auth.UserFromContext(r.Context())
	var input walletBalanceRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	gatewayUserID := strings.TrimSpace(r.PathValue("userID"))
	reason := strings.TrimSpace(input.Reason)
	if reason == "" {
		writeError(w, http.StatusBadRequest, "reason is required")
		return
	}

	var result store.WalletAdjustmentResult
	var auditLog store.AuditLog
	err := s.store.InTx(r.Context(), func(tx store.Tx) error {
		next, err := s.store.SetUserWalletBalanceTx(r.Context(), tx, store.WalletBalanceAdjustmentInput{
			GatewayUserID:  gatewayUserID,
			Currency:       input.Currency,
			BalanceText:    input.Balance.String(),
			Reason:         reason,
			IdempotencyKey: input.IdempotencyKey,
			Metadata:       input.Metadata,
		})
		if err != nil {
			return err
		}
		result = next
		record, err := s.store.RecordAuditLogTx(r.Context(), tx, walletAdjustmentAuditInput(r, actor, reason, result))
		if err != nil {
			return err
		}
		auditLog = record
		return nil
	})
	if err != nil {
		switch {
		case store.IsNotFound(err):
			writeError(w, http.StatusNotFound, "user not found")
		case errors.Is(err, store.ErrWalletBalanceUnchanged):
			writeError(w, http.StatusBadRequest, "wallet balance is unchanged")
		case errors.Is(err, store.ErrInvalidWalletAmount):
			writeError(w, http.StatusBadRequest, "wallet balance must be a non-negative decimal with at most nine fractional digits", "invalid_wallet_amount")
		case errors.Is(err, store.ErrBalanceBelowFrozen):
			writeError(w, http.StatusConflict, "wallet balance cannot be below frozen balance", "balance_below_frozen")
		default:
			s.logger.Error("set user wallet balance failed", "error", err)
			writeError(w, http.StatusInternalServerError, "set user wallet balance failed")
		}
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"account":     result.Account,
		"before":      result.Before,
		"transaction": result.Transaction,
		"auditLog":    auditLog,
	})
}

// rechargeUserWalletBalance godoc
// @Summary 充值用户钱包余额
// @Description 管理端给指定用户钱包追加充值金额，并记录审计日志；amount 必须大于 0。
// @Tags billing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userID path string true "用户 ID"
// @Param input body walletRechargeRequest true "钱包充值请求"
// @Success 200 {object} WalletAdjustmentResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/{userID}/wallet/recharge [post]
func (s *Server) rechargeUserWalletBalance(w http.ResponseWriter, r *http.Request) {
	actor, _ := auth.UserFromContext(r.Context())
	var input walletRechargeRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	gatewayUserID := strings.TrimSpace(r.PathValue("userID"))
	reason := strings.TrimSpace(input.Reason)
	if reason == "" {
		writeError(w, http.StatusBadRequest, "reason is required")
		return
	}

	var result store.WalletAdjustmentResult
	var auditLog store.AuditLog
	err := s.store.InTx(r.Context(), func(tx store.Tx) error {
		next, err := s.store.RechargeUserWalletBalanceTx(r.Context(), tx, store.WalletRechargeInput{
			GatewayUserID:  gatewayUserID,
			Currency:       input.Currency,
			AmountText:     input.Amount.String(),
			Reason:         reason,
			IdempotencyKey: input.IdempotencyKey,
			Metadata:       input.Metadata,
		})
		if err != nil {
			return err
		}
		result = next
		record, err := s.store.RecordAuditLogTx(r.Context(), tx, walletRechargeAuditInput(r, actor, reason, result))
		if err != nil {
			return err
		}
		auditLog = record
		return nil
	})
	if err != nil {
		switch {
		case store.IsNotFound(err):
			writeError(w, http.StatusNotFound, "user not found")
		case errors.Is(err, store.ErrInvalidWalletAmount):
			writeError(w, http.StatusBadRequest, "wallet recharge amount must be a positive decimal with at most nine fractional digits", "invalid_wallet_amount")
		default:
			s.logger.Error("recharge user wallet balance failed", "error", err)
			writeError(w, http.StatusInternalServerError, "recharge user wallet balance failed")
		}
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"account":     result.Account,
		"before":      result.Before,
		"transaction": result.Transaction,
		"auditLog":    auditLog,
	})
}

// listAuditLogs godoc
// @Summary 列出审计日志
// @Description 管理端按分类、动作、目标类型和目标 ID 查询审计日志。
// @Tags billing
// @Produce json
// @Security BearerAuth
// @Param category query string false "审计分类"
// @Param action query string false "审计动作"
// @Param targetType query string false "目标类型"
// @Param targetId query string false "目标 ID"
// @Param limit query int false "返回数量" default(100)
// @Success 200 {object} AuditLogListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/audit-logs [get]
func (s *Server) listAuditLogs(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	limit, err := positiveQueryInt(query.Get("limit"), 100)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid limit")
		return
	}
	items, err := s.store.ListAuditLogs(r.Context(), store.AuditLogFilter{
		Category:   query.Get("category"),
		Action:     query.Get("action"),
		TargetType: query.Get("targetType"),
		TargetID:   query.Get("targetId"),
		Limit:      limit,
	})
	if err != nil {
		s.logger.Error("list audit logs failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list audit logs failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

func walletAdjustmentAuditInput(r *http.Request, actor *auth.User, reason string, result store.WalletAdjustmentResult) store.AuditLogInput {
	actorGatewayUserID := ""
	actorUserID := ""
	actorUsername := ""
	actorSource := ""
	actorRoles := []string(nil)
	if actor != nil {
		actorGatewayUserID = uuidText(firstNonEmptyText(actor.GatewayUserID, actor.ID))
		actorUserID = actor.ID
		actorUsername = actor.Username
		actorSource = actor.Source
		actorRoles = actor.Roles
	}
	return store.AuditLogInput{
		Category:              "billing",
		Action:                "wallet.balance.set",
		ActorGatewayUserID:    actorGatewayUserID,
		ActorUserID:           actorUserID,
		ActorUsername:         actorUsername,
		ActorSource:           actorSource,
		ActorRoles:            actorRoles,
		TargetType:            "gateway_user",
		TargetID:              result.Account.GatewayUserID,
		TargetGatewayUserID:   result.Account.GatewayUserID,
		TargetGatewayTenantID: result.Account.GatewayTenantID,
		RequestIP:             requestIP(r),
		UserAgent:             r.UserAgent(),
		BeforeState: map[string]any{
			"walletAccount": result.Before,
		},
		AfterState: map[string]any{
			"walletAccount": result.Account,
			"transaction":   result.Transaction,
		},
		Metadata: map[string]any{
			"reason":        reason,
			"currency":      result.Account.Currency,
			"transactionId": result.Transaction.ID,
			"amount":        result.Transaction.Amount,
			"direction":     result.Transaction.Direction,
		},
	}
}

func walletRechargeAuditInput(r *http.Request, actor *auth.User, reason string, result store.WalletAdjustmentResult) store.AuditLogInput {
	input := walletAdjustmentAuditInput(r, actor, reason, result)
	input.Action = "wallet.balance.recharge"
	return input
}

func requestIP(r *http.Request) string {
	if forwarded := strings.TrimSpace(r.Header.Get("X-Forwarded-For")); forwarded != "" {
		parts := strings.Split(forwarded, ",")
		return strings.TrimSpace(parts[0])
	}
	if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" {
		return realIP
	}
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err == nil {
		return host
	}
	return strings.TrimSpace(r.RemoteAddr)
}

func firstNonEmptyText(values ...string) string {
	for _, value := range values {
		if text := strings.TrimSpace(value); text != "" {
			return text
		}
	}
	return ""
}

func uuidText(value string) string {
	value = strings.TrimSpace(value)
	if len(value) != 36 {
		return ""
	}
	if value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' {
		return ""
	}
	return value
}
