package hostorg

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/userclient"
	walletv1 "hyapp.local/api/proto/wallet/v1"
)

const (
	salaryWalletRoleHost     = "host"
	salaryWalletRoleAgency   = "agency"
	salaryWalletRoleBD       = "bd"
	salaryWalletRoleBDLeader = "bd_leader"

	assetHostSalaryUSD   = "HOST_SALARY_USD"
	assetAgencySalaryUSD = "AGENCY_SALARY_USD"
	assetBDSalaryUSD     = "BD_SALARY_USD"
	assetAdminSalaryUSD  = "ADMIN_SALARY_USD"

	salaryWalletAdjustIncrease = "increase"
	salaryWalletAdjustDecrease = "decrease"
)

type salaryWalletHistoryQuery struct {
	Page     int
	PageSize int
	Role     string
	UserID   int64
}

type salaryWalletAdjustRequest struct {
	CommandID    string        `json:"commandId"`
	Role         string        `json:"role" binding:"required"`
	TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"`
	Type         string        `json:"type" binding:"required"`
	AmountMinor  int64         `json:"amountMinor" binding:"required"`
	Reason       string        `json:"reason" binding:"required"`
}

type salaryWalletOperatorDTO struct {
	AdminID  string `json:"adminId"`
	Username string `json:"username"`
	Name     string `json:"name"`
}

type salaryWalletHistoryItem struct {
	EntryID            int64                   `json:"entryId"`
	TransactionID      string                  `json:"transactionId"`
	CommandID          string                  `json:"commandId"`
	BizType            string                  `json:"bizType"`
	ExternalRef        string                  `json:"externalRef"`
	Role               string                  `json:"role"`
	UserID             string                  `json:"userId"`
	AssetType          string                  `json:"assetType"`
	Direction          string                  `json:"direction"`
	AmountMinor        int64                   `json:"amountMinor"`
	AvailableDelta     int64                   `json:"availableDelta"`
	AvailableAfter     int64                   `json:"availableAfter"`
	FrozenDelta        int64                   `json:"frozenDelta"`
	FrozenAfter        int64                   `json:"frozenAfter"`
	CounterpartyUserID string                  `json:"counterpartyUserId"`
	RoomID             string                  `json:"roomId"`
	Reason             string                  `json:"reason"`
	OperatorUserID     string                  `json:"operatorUserId"`
	Operator           salaryWalletOperatorDTO `json:"operator"`
	Metadata           map[string]any          `json:"metadata"`
	CreatedAtMS        int64                   `json:"createdAtMs"`
}

type salaryWalletAdjustResult struct {
	TransactionID string                  `json:"transactionId"`
	Role          string                  `json:"role"`
	UserID        string                  `json:"userId"`
	AssetType     string                  `json:"assetType"`
	AmountMinor   int64                   `json:"amountMinor"`
	Reason        string                  `json:"reason"`
	Balance       userclient.SalaryWallet `json:"balance"`
}

func (r *Reader) fillBDProfileSalaryWallets(ctx context.Context, role string, items []*userclient.BDProfile) error {
	userIDs := make([]int64, 0, len(items))
	for _, item := range items {
		if item.UserID > 0 {
			userIDs = append(userIDs, item.UserID)
		}
	}
	wallets, err := r.salaryWalletBalances(ctx, appctx.FromContext(ctx), role, userIDs)
	if err != nil {
		return err
	}
	for _, item := range items {
		item.SalaryWallet = wallets[item.UserID]
	}
	return nil
}

func (r *Reader) fillAgencySalaryWallets(ctx context.Context, items []*userclient.Agency) error {
	userIDs := make([]int64, 0, len(items))
	for _, item := range items {
		if item.OwnerUserID > 0 {
			userIDs = append(userIDs, item.OwnerUserID)
		}
	}
	wallets, err := r.salaryWalletBalances(ctx, appctx.FromContext(ctx), salaryWalletRoleAgency, userIDs)
	if err != nil {
		return err
	}
	for _, item := range items {
		item.SalaryWallet = wallets[item.OwnerUserID]
	}
	return nil
}

func (r *Reader) fillHostSalaryWallets(ctx context.Context, items []*userclient.HostProfile) error {
	userIDs := make([]int64, 0, len(items))
	for _, item := range items {
		if item.UserID > 0 {
			userIDs = append(userIDs, item.UserID)
		}
	}
	wallets, err := r.salaryWalletBalances(ctx, appctx.FromContext(ctx), salaryWalletRoleHost, userIDs)
	if err != nil {
		return err
	}
	for _, item := range items {
		item.SalaryWallet = wallets[item.UserID]
	}
	return nil
}

func (r *Reader) salaryWalletBalances(ctx context.Context, appCode string, role string, userIDs []int64) (map[int64]userclient.SalaryWallet, error) {
	role = normalizeSalaryWalletRole(role)
	assetType := salaryWalletAssetType(role)
	result := make(map[int64]userclient.SalaryWallet, len(userIDs))
	for _, userID := range positiveUniqueIDs(userIDs) {
		// 列表必须稳定返回该身份对应的钱包对象；没有 wallet_accounts 行时余额就是 0，而不是让前端猜资产类型。
		result[userID] = userclient.SalaryWallet{Role: role, UserID: userID, AssetType: assetType}
	}
	if r == nil || r.walletDB == nil || len(result) == 0 {
		return result, nil
	}
	args := []any{appCode, assetType}
	for userID := range result {
		args = append(args, userID)
	}
	rows, err := r.walletDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT user_id, available_amount, frozen_amount, updated_at_ms
		FROM wallet_accounts
		WHERE app_code = ? AND asset_type = ? AND user_id IN (%s)
	`, sqlPlaceholders(len(result))), args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var userID int64
		wallet := userclient.SalaryWallet{Role: role, AssetType: assetType}
		if err := rows.Scan(&userID, &wallet.AvailableAmount, &wallet.FrozenAmount, &wallet.UpdatedAtMs); err != nil {
			return nil, err
		}
		wallet.UserID = userID
		result[userID] = wallet
	}
	return result, rows.Err()
}

func (s *Service) ListSalaryWalletHistory(ctx context.Context, query salaryWalletHistoryQuery) ([]salaryWalletHistoryItem, int64, error) {
	query = normalizeSalaryWalletHistoryQuery(query)
	if s == nil || s.reader == nil || s.reader.walletDB == nil {
		return nil, 0, fmt.Errorf("wallet mysql is not configured")
	}
	if query.Role == "" || query.UserID <= 0 {
		return nil, 0, fmt.Errorf("role and user_id are required")
	}
	// 历史查询先验证用户确实拥有对应组织身份，避免拿 host 身份去看 agency/BD 的工资资产流水。
	if err := s.reader.requireSalaryWalletIdentity(ctx, query.Role, query.UserID); err != nil {
		return nil, 0, err
	}
	return s.reader.listSalaryWalletHistory(ctx, query)
}

func (r *Reader) listSalaryWalletHistory(ctx context.Context, query salaryWalletHistoryQuery) ([]salaryWalletHistoryItem, int64, error) {
	appCode := appctx.FromContext(ctx)
	assetType := salaryWalletAssetType(query.Role)
	args := []any{appCode, query.UserID, assetType}
	whereSQL := `WHERE e.app_code = ? AND e.user_id = ? AND e.asset_type = ?`
	var total int64
	if err := r.walletDB.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM wallet_entries e
		JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
		`+whereSQL, args...,
	).Scan(&total); err != nil {
		return nil, 0, err
	}
	rows, err := r.walletDB.QueryContext(ctx, `
		SELECT e.entry_id, e.transaction_id, wt.command_id, wt.biz_type, wt.external_ref,
		       e.available_delta, e.available_after, e.frozen_delta, e.frozen_after,
		       e.counterparty_user_id, e.room_id, COALESCE(CAST(wt.metadata_json AS CHAR), '{}'), e.created_at_ms
		FROM wallet_entries e
		JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
		`+whereSQL+`
		ORDER BY e.created_at_ms DESC, e.entry_id DESC
		LIMIT ? OFFSET ?`, append(args, query.PageSize, offset(query.Page, query.PageSize))...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]salaryWalletHistoryItem, 0, query.PageSize)
	operatorIDs := make([]int64, 0, query.PageSize)
	for rows.Next() {
		var item salaryWalletHistoryItem
		var counterpartyUserID int64
		var metadataJSON string
		if err := rows.Scan(
			&item.EntryID,
			&item.TransactionID,
			&item.CommandID,
			&item.BizType,
			&item.ExternalRef,
			&item.AvailableDelta,
			&item.AvailableAfter,
			&item.FrozenDelta,
			&item.FrozenAfter,
			&counterpartyUserID,
			&item.RoomID,
			&metadataJSON,
			&item.CreatedAtMS,
		); err != nil {
			return nil, 0, err
		}
		metadata, err := parseSalaryWalletMetadata(metadataJSON)
		if err != nil {
			return nil, 0, err
		}
		operatorID := salaryWalletMetadataInt64(metadata, "operator_user_id", "operatorUserId")
		if operatorID > 0 {
			operatorIDs = append(operatorIDs, operatorID)
		}
		item.Role = query.Role
		item.UserID = strconv.FormatInt(query.UserID, 10)
		item.AssetType = assetType
		item.Direction = salaryWalletDirection(item.AvailableDelta)
		item.AmountMinor = item.AvailableDelta
		item.CounterpartyUserID = formatSalaryWalletID(counterpartyUserID)
		item.Reason = salaryWalletMetadataString(metadata, "reason")
		item.OperatorUserID = formatSalaryWalletID(operatorID)
		item.Metadata = metadata
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	operators, err := r.salaryWalletAdminProfiles(ctx, operatorIDs)
	if err != nil {
		return nil, 0, err
	}
	for i := range items {
		operatorID, _ := strconv.ParseInt(items[i].OperatorUserID, 10, 64)
		if operator, ok := operators[operatorID]; ok {
			items[i].Operator = operator
		}
	}
	return items, total, nil
}

func (s *Service) AdjustSalaryWallet(ctx context.Context, actorID int64, requestID string, req salaryWalletAdjustRequest) (salaryWalletAdjustResult, error) {
	if s == nil || s.walletClient == nil || s.reader == nil {
		return salaryWalletAdjustResult{}, fmt.Errorf("wallet client is not configured")
	}
	role := normalizeSalaryWalletRole(req.Role)
	targetUserID := req.TargetUserID.Int64()
	if role == "" || targetUserID <= 0 || req.AmountMinor <= 0 {
		return salaryWalletAdjustResult{}, fmt.Errorf("role, target_user_id and amount are required")
	}
	if actorID <= 0 {
		return salaryWalletAdjustResult{}, fmt.Errorf("operator_user_id is required")
	}
	reason := strings.TrimSpace(req.Reason)
	if reason == "" {
		return salaryWalletAdjustResult{}, fmt.Errorf("reason is required")
	}
	if len(reason) > 512 {
		return salaryWalletAdjustResult{}, fmt.Errorf("reason is too long")
	}
	// 调整工资钱包前必须确认目标用户具备该身份；同一个 user_id 可能同时有多种身份，资产类型不能由前端随意指定。
	if err := s.reader.requireSalaryWalletIdentity(ctx, role, targetUserID); err != nil {
		return salaryWalletAdjustResult{}, err
	}
	amount := req.AmountMinor
	adjustType := strings.ToLower(strings.TrimSpace(req.Type))
	switch adjustType {
	case salaryWalletAdjustIncrease:
	case salaryWalletAdjustDecrease:
		// wallet-service 的 AdminCreditAsset 以带符号金额入账，扣减也走同一条幂等账务链路并由钱包层做余额不透支校验。
		amount = -amount
	default:
		return salaryWalletAdjustResult{}, fmt.Errorf("salary adjustment type is invalid")
	}
	commandID := strings.TrimSpace(req.CommandID)
	if commandID == "" {
		commandID = defaultSalaryWalletCommandID(requestID)
	}
	if len(commandID) > 128 {
		return salaryWalletAdjustResult{}, fmt.Errorf("command_id is too long")
	}
	assetType := salaryWalletAssetType(role)
	// 手工增减工资只提交目标身份对应的工资资产；交易、分录、余额和幂等命令统一由 wallet-service 落库。
	resp, err := s.walletClient.AdminCreditAsset(ctx, &walletv1.AdminCreditAssetRequest{
		CommandId:      commandID,
		TargetUserId:   targetUserID,
		AssetType:      assetType,
		Amount:         amount,
		OperatorUserId: actorID,
		Reason:         reason,
		EvidenceRef:    defaultSalaryWalletEvidenceRef(requestID),
		AppCode:        appctx.FromContext(ctx),
	})
	if err != nil {
		return salaryWalletAdjustResult{}, err
	}
	wallet := userclient.SalaryWallet{
		Role:            role,
		UserID:          targetUserID,
		AssetType:       assetType,
		AvailableAmount: 0,
		FrozenAmount:    0,
		UpdatedAtMs:     time.Now().UnixMilli(),
	}
	if balance := resp.GetBalance(); balance != nil {
		wallet.AvailableAmount = balance.GetAvailableAmount()
		wallet.FrozenAmount = balance.GetFrozenAmount()
	}
	return salaryWalletAdjustResult{
		TransactionID: resp.GetTransactionId(),
		Role:          role,
		UserID:        strconv.FormatInt(targetUserID, 10),
		AssetType:     assetType,
		AmountMinor:   amount,
		Reason:        reason,
		Balance:       wallet,
	}, nil
}

func (r *Reader) requireSalaryWalletIdentity(ctx context.Context, role string, userID int64) error {
	if r == nil || r.db == nil {
		return errUserDBNotConfigured()
	}
	if userID <= 0 {
		return fmt.Errorf("user_id is required")
	}
	appCode := appctx.FromContext(ctx)
	var query string
	args := []any{appCode, userID}
	switch normalizeSalaryWalletRole(role) {
	case salaryWalletRoleHost:
		query = `SELECT COUNT(1) FROM host_profiles WHERE app_code = ? AND user_id = ?`
	case salaryWalletRoleAgency:
		query = `SELECT COUNT(1) FROM agencies WHERE app_code = ? AND owner_user_id = ? AND status <> 'deleted'`
	case salaryWalletRoleBD:
		query = `SELECT COUNT(1) FROM bd_profiles WHERE app_code = ? AND user_id = ? AND role = 'bd'`
	case salaryWalletRoleBDLeader:
		query = `SELECT COUNT(1) FROM bd_leader_profiles WHERE app_code = ? AND user_id = ?`
	default:
		return fmt.Errorf("salary wallet role is invalid")
	}
	var count int
	if err := r.db.QueryRowContext(ctx, query, args...).Scan(&count); err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("salary wallet identity not found")
	}
	return nil
}

func (r *Reader) salaryWalletAdminProfiles(ctx context.Context, adminIDs []int64) (map[int64]salaryWalletOperatorDTO, error) {
	result := make(map[int64]salaryWalletOperatorDTO, len(adminIDs))
	if r == nil || r.adminDB == nil {
		return result, nil
	}
	adminIDs = positiveUniqueIDs(adminIDs)
	if len(adminIDs) == 0 {
		return result, nil
	}
	args := make([]any, 0, len(adminIDs))
	for _, id := range adminIDs {
		args = append(args, id)
	}
	rows, err := r.adminDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, COALESCE(username, ''), COALESCE(name, '')
		FROM admin_users
		WHERE id IN (%s)
	`, sqlPlaceholders(len(adminIDs))), args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var id int64
		var item salaryWalletOperatorDTO
		if err := rows.Scan(&id, &item.Username, &item.Name); err != nil {
			return nil, err
		}
		item.AdminID = strconv.FormatInt(id, 10)
		result[id] = item
	}
	return result, rows.Err()
}

func normalizeSalaryWalletHistoryQuery(query salaryWalletHistoryQuery) salaryWalletHistoryQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.Role = normalizeSalaryWalletRole(query.Role)
	return query
}

func normalizeSalaryWalletRole(role string) string {
	switch strings.ToLower(strings.TrimSpace(role)) {
	case salaryWalletRoleHost:
		return salaryWalletRoleHost
	case salaryWalletRoleAgency:
		return salaryWalletRoleAgency
	case salaryWalletRoleBD:
		return salaryWalletRoleBD
	case salaryWalletRoleBDLeader, "leader", "bdleader", "admin":
		return salaryWalletRoleBDLeader
	default:
		return ""
	}
}

func salaryWalletAssetType(role string) string {
	switch normalizeSalaryWalletRole(role) {
	case salaryWalletRoleHost:
		return assetHostSalaryUSD
	case salaryWalletRoleAgency:
		return assetAgencySalaryUSD
	case salaryWalletRoleBD:
		return assetBDSalaryUSD
	case salaryWalletRoleBDLeader:
		// BD Leader 在工资政策里沿用 admin 结算身份，所以列表和历史都读取 ADMIN_SALARY_USD。
		return assetAdminSalaryUSD
	default:
		return ""
	}
}

func salaryWalletDirection(delta int64) string {
	if delta < 0 {
		return "expense"
	}
	return "income"
}

func parseSalaryWalletMetadata(value string) (map[string]any, error) {
	value = strings.TrimSpace(value)
	if value == "" || value == "null" {
		return map[string]any{}, nil
	}
	var metadata map[string]any
	if err := json.Unmarshal([]byte(value), &metadata); err != nil {
		return nil, err
	}
	if metadata == nil {
		return map[string]any{}, nil
	}
	return metadata, nil
}

func salaryWalletMetadataString(metadata map[string]any, key string) string {
	value, ok := metadata[key]
	if !ok || value == nil {
		return ""
	}
	if text, ok := value.(string); ok {
		return strings.TrimSpace(text)
	}
	return strings.TrimSpace(fmt.Sprint(value))
}

func salaryWalletMetadataInt64(metadata map[string]any, keys ...string) int64 {
	for _, key := range keys {
		value, ok := metadata[key]
		if !ok || value == nil {
			continue
		}
		switch typed := value.(type) {
		case float64:
			return int64(typed)
		case int64:
			return typed
		case int:
			return int64(typed)
		case string:
			parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
			if err == nil {
				return parsed
			}
		}
	}
	return 0
}

func formatSalaryWalletID(value int64) string {
	if value <= 0 {
		return ""
	}
	return strconv.FormatInt(value, 10)
}

func positiveUniqueIDs(values []int64) []int64 {
	seen := make(map[int64]struct{}, len(values))
	result := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}

func defaultSalaryWalletCommandID(requestID string) string {
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		return "salary-wallet-adjustment-" + strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
	}
	return "salary-wallet-adjustment-" + requestID
}

func defaultSalaryWalletEvidenceRef(requestID string) string {
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		return "salary-wallet-adjustment"
	}
	return "admin-request:" + requestID
}
