package hostsalarysettlement

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
)

const maxPageSize = 100

type Service struct {
	walletDB *sql.DB
	userDB   *sql.DB
}

type userProfile struct {
	UserID        int64
	DisplayUserID string
	Username      string
	Avatar        string
}

func NewService(walletDB *sql.DB, userDB *sql.DB) *Service {
	return &Service{walletDB: walletDB, userDB: userDB}
}

func (s *Service) List(ctx context.Context, appCode string, req query) ([]settlementDTO, int64, error) {
	if s == nil || s.walletDB == nil {
		return nil, 0, fmt.Errorf("wallet mysql is not configured")
	}
	req = normalizeQuery(req)
	whereSQL, args := settlementWhere(appCode, req)

	var total int64
	if err := s.walletDB.QueryRowContext(ctx, `SELECT COUNT(*) FROM host_salary_settlement_records `+whereSQL, args...).Scan(&total); err != nil {
		return nil, 0, err
	}
	rows, err := s.walletDB.QueryContext(ctx, `
		SELECT settlement_id, command_id, transaction_id, settlement_type, user_id, agency_owner_user_id,
		       cycle_key, policy_id, level_no, total_diamonds, host_salary_usd_minor_delta,
		       host_coin_reward_delta, agency_salary_usd_minor_delta, residual_usd_minor_delta,
		       status, reason, created_at_ms
		FROM host_salary_settlement_records
		`+whereSQL+`
		ORDER BY created_at_ms DESC, settlement_id DESC
		LIMIT ? OFFSET ?`,
		append(args, req.PageSize, offset(req.Page, req.PageSize))...,
	)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]settlementDTO, 0, req.PageSize)
	userIDs := make([]int64, 0, req.PageSize*2)
	for rows.Next() {
		var item settlementDTO
		var userID int64
		var agencyOwnerUserID int64
		if err := rows.Scan(
			&item.SettlementID,
			&item.CommandID,
			&item.TransactionID,
			&item.SettlementType,
			&userID,
			&agencyOwnerUserID,
			&item.CycleKey,
			&item.PolicyID,
			&item.LevelNo,
			&item.TotalDiamonds,
			&item.HostSalaryUSDMinorDelta,
			&item.HostCoinRewardDelta,
			&item.AgencySalaryUSDMinorDelta,
			&item.ResidualUSDMinorDelta,
			&item.Status,
			&item.Reason,
			&item.CreatedAtMS,
		); err != nil {
			return nil, 0, err
		}
		item.UserID = strconv.FormatInt(userID, 10)
		item.AgencyOwnerUserID = formatOptionalUserID(agencyOwnerUserID)
		item.User = userDTO{UserID: item.UserID}
		item.AgencyOwner = userDTO{UserID: item.AgencyOwnerUserID}
		item.HostSalaryUSDDelta = formatUSDMinor(item.HostSalaryUSDMinorDelta)
		item.AgencySalaryUSDDelta = formatUSDMinor(item.AgencySalaryUSDMinorDelta)
		item.ResidualUSDDelta = formatUSDMinor(item.ResidualUSDMinorDelta)
		items = append(items, item)
		if userID > 0 {
			userIDs = append(userIDs, userID)
		}
		if agencyOwnerUserID > 0 {
			userIDs = append(userIDs, agencyOwnerUserID)
		}
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}

	profiles, err := s.userProfiles(ctx, appCode, userIDs)
	if err != nil {
		return nil, 0, err
	}
	for i := range items {
		if userID, err := strconv.ParseInt(items[i].UserID, 10, 64); err == nil {
			items[i].User = userDTOFromProfile(items[i].UserID, profiles[userID])
		}
		if agencyID, err := strconv.ParseInt(items[i].AgencyOwnerUserID, 10, 64); err == nil && agencyID > 0 {
			items[i].AgencyOwner = userDTOFromProfile(items[i].AgencyOwnerUserID, profiles[agencyID])
		}
	}
	return items, total, nil
}

func settlementWhere(appCode string, req query) (string, []any) {
	conditions := []string{"app_code = ?"}
	args := []any{strings.TrimSpace(appCode)}
	if req.CycleKey != "" {
		conditions = append(conditions, "cycle_key = ?")
		args = append(args, req.CycleKey)
	}
	if req.SettlementType != "" {
		conditions = append(conditions, "settlement_type = ?")
		args = append(args, req.SettlementType)
	}
	if req.Status != "" {
		conditions = append(conditions, "status = ?")
		args = append(args, req.Status)
	}
	if req.UserID > 0 {
		conditions = append(conditions, "user_id = ?")
		args = append(args, req.UserID)
	}
	if req.AgencyOwnerUserID > 0 {
		conditions = append(conditions, "agency_owner_user_id = ?")
		args = append(args, req.AgencyOwnerUserID)
	}
	if req.PolicyID > 0 {
		conditions = append(conditions, "policy_id = ?")
		args = append(args, req.PolicyID)
	}
	if req.StartAtMS > 0 {
		conditions = append(conditions, "created_at_ms >= ?")
		args = append(args, req.StartAtMS)
	}
	if req.EndAtMS > 0 {
		conditions = append(conditions, "created_at_ms < ?")
		args = append(args, req.EndAtMS)
	}
	// 查询只读 wallet-service 结算事实表，不回推钱包分录；账务对账需要单独走 entries/transactions 明细。
	return "WHERE " + strings.Join(conditions, " AND "), args
}

func (s *Service) userProfiles(ctx context.Context, appCode string, userIDs []int64) (map[int64]userProfile, error) {
	if s == nil || s.userDB == nil || len(userIDs) == 0 {
		return map[int64]userProfile{}, nil
	}
	uniqueIDs := uniquePositiveUserIDs(userIDs)
	if len(uniqueIDs) == 0 {
		return map[int64]userProfile{}, nil
	}
	placeholders := strings.TrimRight(strings.Repeat("?,", len(uniqueIDs)), ",")
	args := make([]any, 0, len(uniqueIDs)+1)
	args = append(args, strings.TrimSpace(appCode))
	for _, id := range uniqueIDs {
		args = append(args, id)
	}
	rows, err := s.userDB.QueryContext(ctx, `
		SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '')
		FROM users
		WHERE app_code = ? AND user_id IN (`+placeholders+`)`,
		args...,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	profiles := make(map[int64]userProfile, len(uniqueIDs))
	for rows.Next() {
		var item userProfile
		if err := rows.Scan(&item.UserID, &item.DisplayUserID, &item.Username, &item.Avatar); err != nil {
			return nil, err
		}
		profiles[item.UserID] = item
	}
	return profiles, rows.Err()
}

func normalizeQuery(req query) query {
	req.CycleKey = strings.TrimSpace(req.CycleKey)
	req.SettlementType = normalizeSettlementType(req.SettlementType)
	req.Status = normalizeStatus(req.Status)
	if req.Page <= 0 {
		req.Page = 1
	}
	if req.PageSize <= 0 {
		req.PageSize = 50
	}
	if req.PageSize > maxPageSize {
		req.PageSize = maxPageSize
	}
	return req
}

func normalizeSettlementType(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", "all":
		return ""
	case "daily", "day":
		return "daily"
	case "half_month", "half-month", "halfmonth":
		return "half_month"
	case "month_end", "month-end", "monthend":
		return "month_end"
	default:
		return ""
	}
}

func normalizeStatus(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", "all":
		return ""
	case "succeeded", "skipped", "failed":
		return strings.ToLower(strings.TrimSpace(value))
	default:
		return ""
	}
}

func uniquePositiveUserIDs(ids []int64) []int64 {
	seen := map[int64]struct{}{}
	out := make([]int64, 0, len(ids))
	for _, id := range ids {
		if id <= 0 {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		out = append(out, id)
	}
	return out
}

func userDTOFromProfile(fallbackID string, profile userProfile) userDTO {
	if profile.UserID <= 0 {
		return userDTO{UserID: fallbackID}
	}
	return userDTO{
		UserID:        strconv.FormatInt(profile.UserID, 10),
		DisplayUserID: profile.DisplayUserID,
		Username:      profile.Username,
		Avatar:        profile.Avatar,
	}
}

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

func formatUSDMinor(value int64) string {
	sign := ""
	if value < 0 {
		sign = "-"
		value = -value
	}
	whole := value / 100
	fraction := value % 100
	if fraction == 0 {
		return sign + strconv.FormatInt(whole, 10)
	}
	if fraction < 10 {
		return sign + strconv.FormatInt(whole, 10) + ".0" + strconv.FormatInt(fraction, 10)
	}
	return sign + strconv.FormatInt(whole, 10) + "." + strconv.FormatInt(fraction, 10)
}

func offset(page int, pageSize int) int {
	if page <= 1 {
		return 0
	}
	return (page - 1) * pageSize
}
