package financewithdrawal

import (
	"errors"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/integration/walletclient"
	"hyapp-admin-server/internal/integration/withdrawalsource"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/repository"
	"hyapp-admin-server/internal/response"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type Handler struct {
	service *Service
	audit   shared.OperationLogger
	sources *withdrawalsource.Registry
}

func New(store *repository.Store, wallet walletclient.Client, activity activityclient.Client, audit shared.OperationLogger) *Handler {
	return NewWithSources(store, wallet, activity, audit, nil)
}

func NewWithSources(store *repository.Store, wallet walletclient.Client, activity activityclient.Client, audit shared.OperationLogger, sources *withdrawalsource.Registry) *Handler {
	return &Handler{service: NewServiceWithSources(store, wallet, activity, sources), audit: audit, sources: sources}
}

func (h *Handler) BindReviewPendingNotifier(notifier ReviewPendingNotifier) *Handler {
	if h != nil && h.service != nil {
		h.service.BindReviewPendingNotifier(notifier)
	}
	return h
}

// CreateExternalApplication 是来源钱包的服务间入口，不使用后台 JWT。
// 每个 App 使用独立 submit token，且业务唯一键保证来源端持久重试不会生成重复审核单。
func (h *Handler) CreateExternalApplication(c *gin.Context) {
	var req struct {
		AppCode             string `json:"appCode" binding:"required"`
		SourceSystem        string `json:"sourceSystem" binding:"required"`
		SourceApplicationID string `json:"sourceApplicationId" binding:"required"`
		UserID              string `json:"userId" binding:"required"`
		SalaryAssetType     string `json:"salaryAssetType" binding:"required"`
		WithdrawAmount      string `json:"withdrawAmount" binding:"required"`
		WithdrawAmountMinor int64  `json:"withdrawAmountMinor" binding:"required"`
		BalanceBefore       string `json:"balanceBefore"`
		ServiceChargeRatio  string `json:"serviceChargeRatio"`
		ServiceCharge       string `json:"serviceCharge"`
		ActualAmount        string `json:"actualAmount"`
		ActualAmountMinor   *int64 `json:"actualAmountMinor"`
		WithdrawMethod      string `json:"withdrawMethod" binding:"required"`
		WithdrawAddress     string `json:"withdrawAddress" binding:"required"`
		CreatedAtMS         int64  `json:"createdAtMs"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "提现申请参数不正确")
		return
	}
	if h.sources == nil || !h.sources.AuthenticateSubmit(req.AppCode, req.SourceSystem, c.GetHeader("Authorization")) {
		response.Unauthorized(c, "提现来源鉴权失败")
		return
	}
	item, err := h.service.CreateExternalApplication(c.Request.Context(), externalWithdrawalApplicationInput{
		AppCode:             req.AppCode,
		SourceSystem:        req.SourceSystem,
		SourceApplicationID: req.SourceApplicationID,
		UserID:              req.UserID,
		SalaryAssetType:     req.SalaryAssetType,
		WithdrawAmount:      req.WithdrawAmount,
		WithdrawAmountMinor: req.WithdrawAmountMinor,
		BalanceBefore:       req.BalanceBefore,
		ServiceChargeRatio:  req.ServiceChargeRatio,
		ServiceCharge:       req.ServiceCharge,
		ActualAmount:        req.ActualAmount,
		ActualAmountMinor:   req.ActualAmountMinor,
		WithdrawMethod:      req.WithdrawMethod,
		WithdrawAddress:     req.WithdrawAddress,
		CreatedAtMS:         req.CreatedAtMS,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	response.Created(c, item)
}

func (h *Handler) ListApplications(c *gin.Context) {
	h.listApplications(c, false)
}

func (h *Handler) ListOperationsApplications(c *gin.Context) {
	h.listApplications(c, true)
}

func (h *Handler) listApplications(c *gin.Context, operations bool) {
	options := shared.ListOptions(c)
	listOptions := repository.WithdrawalApplicationListOptions{
		Page:     options.Page,
		PageSize: options.PageSize,
		AppCode:  firstQuery(c, "app_code", "appCode"),
		Keyword:  options.Keyword,
	}
	var (
		items []withdrawalApplicationDTO
		total int64
		err   error
	)
	actor := shared.ActorFromContext(c)
	if operations {
		items, total, err = h.service.ListOperationsApplications(actor, listOptions)
	} else {
		items, total, err = h.service.ListApplications(actor, listOptions)
	}
	if err != nil {
		if errors.Is(err, errWithdrawalMoneyScopeForbidden) {
			response.Forbidden(c, err.Error())
			return
		}
		response.ServerError(c, "获取用户提现申请列表失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total})
}

func (h *Handler) ApproveApplication(c *gin.Context) {
	h.auditApplication(c, "approved", false)
}

func (h *Handler) RejectApplication(c *gin.Context) {
	h.auditApplication(c, "rejected", false)
}

func (h *Handler) ApproveOperationsApplication(c *gin.Context) {
	h.auditApplication(c, "approved", true)
}

func (h *Handler) RejectOperationsApplication(c *gin.Context) {
	h.auditApplication(c, "rejected", true)
}

func (h *Handler) auditApplication(c *gin.Context, decision string, operations bool) {
	id, ok := shared.ParseID(c, "application_id")
	if !ok {
		return
	}
	if strings.TrimSpace(c.GetHeader(appctx.HeaderAppCode)) == "" {
		// 财务工作台可跨 App 列表，审核时必须由前端显式选中目标行 App；不能让 appctx 的 lalu 默认值静默决定资金数据域。
		response.BadRequest(c, "缺少 "+appctx.HeaderAppCode)
		return
	}
	var req struct {
		AuditRemark        string `json:"auditRemark"`
		AuditImageURL      string `json:"auditImageUrl"`
		AuditImageURLSnake string `json:"audit_image_url"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "审核参数不正确")
		return
	}
	var (
		item *withdrawalApplicationDTO
		err  error
	)
	if operations && decision == "approved" {
		item, err = h.service.ApproveOperationsApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c))
	} else if operations {
		item, err = h.service.RejectOperationsApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c))
	} else if decision == "approved" {
		item, err = h.service.ApproveApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, firstNonEmpty(req.AuditImageURL, req.AuditImageURLSnake), middleware.CurrentRequestID(c))
	} else {
		item, err = h.service.RejectApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c))
	}
	if err != nil {
		writeWithdrawalServiceError(c, err)
		return
	}
	action := "audit-user-withdrawal-finance"
	if operations {
		action = "audit-user-withdrawal-operations"
	}
	shared.OperationLogWithResourceID(c, h.audit, action, "admin_user_withdrawal_applications", strconv.FormatUint(uint64(item.ID), 10), "success", decision)
	response.OK(c, item)
}

func writeWithdrawalServiceError(c *gin.Context, err error) {
	if errors.Is(err, gorm.ErrRecordNotFound) {
		response.NotFound(c, "用户提现申请不存在")
		return
	}
	if errors.Is(err, repository.ErrWithdrawalApplicationAlreadyAudited) {
		response.BadRequest(c, "提现申请已审核")
		return
	}
	if errors.Is(err, repository.ErrWithdrawalApplicationStageNotReviewable) {
		response.BadRequest(c, "提现申请尚未进入当前审核阶段")
		return
	}
	if errors.Is(err, errWithdrawalMoneyScopeForbidden) {
		response.Forbidden(c, err.Error())
		return
	}
	switch strings.TrimSpace(err.Error()) {
	case "没有操作权限":
		response.Forbidden(c, err.Error())
	case "admin finance withdrawal service is not configured":
		response.ServerError(c, "审核用户提现申请失败")
	default:
		response.BadRequest(c, err.Error())
	}
}

func firstQuery(c *gin.Context, keys ...string) string {
	for _, key := range keys {
		if value := strings.TrimSpace(c.Query(key)); value != "" {
			return value
		}
	}
	return ""
}

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