package pointwithdrawalconfig

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

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

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

// List 按 app_code 读取同一套公共配置表，并补齐用户资料；孤儿配置仍返回，便于运营删除而不是静默隐藏。
func (s *Service) List(ctx context.Context, appCode string) ([]itemDTO, error) {
	if err := s.ensureSchema(ctx); err != nil {
		return nil, err
	}
	rows, err := s.walletDB.QueryContext(ctx, `
		SELECT app_code, seller_user_id, sort_order, point_amount, seller_coin_amount,
		       COALESCE(service_region_ids, JSON_ARRAY()), service_country_codes,
		       status, created_at_ms, updated_at_ms
		FROM point_withdrawal_coin_seller_configs
		WHERE app_code = ?
		ORDER BY sort_order DESC, seller_user_id ASC`, normalizeAppCode(appCode))
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]itemDTO, 0)
	for rows.Next() {
		var item itemDTO
		var sellerUserID int64
		var regionsJSON []byte
		var countriesJSON []byte
		if err := rows.Scan(&item.AppCode, &sellerUserID, &item.SortOrder, &item.PointAmount, &item.SellerCoinAmount, &regionsJSON, &countriesJSON, &item.Status, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
			return nil, err
		}
		item.SellerUserID = strconv.FormatInt(sellerUserID, 10)
		if err := json.Unmarshal(regionsJSON, &item.ServiceRegionIDs); err != nil {
			return nil, fmt.Errorf("parse service regions for seller %d: %w", sellerUserID, err)
		}
		if err := json.Unmarshal(countriesJSON, &item.ServiceCountryCodes); err != nil {
			return nil, fmt.Errorf("parse service countries for seller %d: %w", sellerUserID, err)
		}
		item.ServiceRegionIDs = normalizeRegionIDs(item.ServiceRegionIDs)
		item.ServiceCountryCodes = normalizeCountries(item.ServiceCountryCodes)
		displayUserID, nickname, avatar, countryName, lookupErr := s.lookupSeller(ctx, item.AppCode, sellerUserID, false)
		if lookupErr != nil && !errors.Is(lookupErr, sql.ErrNoRows) {
			return nil, lookupErr
		}
		item.DisplayUserID, item.Nickname, item.Avatar, item.CountryName = displayUserID, nickname, avatar, countryName
		items = append(items, item)
	}
	return items, rows.Err()
}

// Upsert 在写白名单前验证同 App 的 active 币商身份，防止普通用户被配置成账务收款方。
func (s *Service) Upsert(ctx context.Context, appCode string, req upsertRequest, adminID int64) (itemDTO, error) {
	appCode = normalizeAppCode(appCode)
	if req.SellerUserID <= 0 || req.PointAmount <= 0 || req.SellerCoinAmount <= 0 || req.SortOrder < 0 {
		return itemDTO{}, errors.New("币商、排序和兑换比例必须是正数")
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status == "" {
		status = "active"
	}
	if status != "active" && status != "disabled" {
		return itemDTO{}, errors.New("状态只能是 active 或 disabled")
	}
	resolvedSellerUserID, err := s.resolveActiveSellerUserID(ctx, appCode, req.SellerUserID)
	if err != nil {
		return itemDTO{}, err
	}
	// 后台运营看到并输入的是短展示号，而提现配置必须持久化不可变的内部 user_id；解析后统一覆盖请求值，
	// 避免展示号变更时配置失联，也避免 18 位内部 ID 经过浏览器 Number 后发生精度损失。
	req.SellerUserID = resolvedSellerUserID
	if err := s.ensureSchema(ctx); err != nil {
		return itemDTO{}, err
	}
	// 非法值不能在规范化时被静默丢弃，否则“输入错误”会意外扩大成全部区域、全部国家。
	for _, regionID := range req.ServiceRegionIDs {
		if regionID <= 0 {
			return itemDTO{}, errors.New("服务区域 ID 不正确")
		}
	}
	for _, countryCode := range req.ServiceCountryCodes {
		countryCode = strings.TrimSpace(countryCode)
		if countryCode == "" || len(countryCode) > 3 {
			return itemDTO{}, errors.New("服务国家不正确")
		}
	}
	regions := normalizeRegionIDs(req.ServiceRegionIDs)
	countries := normalizeCountries(req.ServiceCountryCodes)
	if len(regions) > 0 && len(countries) > 0 {
		return itemDTO{}, errors.New("服务区域和服务国家不能同时选择")
	}
	// 运营只能选择当前 App 的有效主数据；拒绝已停用或不存在的值，避免资金资格配置悬空。
	if err := s.validateServiceScope(ctx, appCode, regions, countries); err != nil {
		return itemDTO{}, err
	}
	regionsJSON, err := json.Marshal(regions)
	if err != nil {
		return itemDTO{}, err
	}
	countriesJSON, err := json.Marshal(countries)
	if err != nil {
		return itemDTO{}, err
	}
	nowMS := time.Now().UTC().UnixMilli()
	_, err = s.walletDB.ExecContext(ctx, `
		INSERT INTO point_withdrawal_coin_seller_configs (
		  app_code, seller_user_id, sort_order, point_amount, seller_coin_amount,
		  service_country_codes, service_region_ids, status,
		  created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
		  sort_order = VALUES(sort_order), point_amount = VALUES(point_amount),
		  seller_coin_amount = VALUES(seller_coin_amount), service_country_codes = VALUES(service_country_codes),
		  service_region_ids = VALUES(service_region_ids),
		  status = VALUES(status), updated_by_admin_id = VALUES(updated_by_admin_id), updated_at_ms = VALUES(updated_at_ms)`,
		appCode, req.SellerUserID, req.SortOrder, req.PointAmount, req.SellerCoinAmount, countriesJSON, regionsJSON,
		status, adminID, adminID, nowMS, nowMS)
	if err != nil {
		return itemDTO{}, err
	}
	return s.Get(ctx, appCode, req.SellerUserID)
}

func (s *Service) Get(ctx context.Context, appCode string, sellerUserID int64) (itemDTO, error) {
	items, err := s.List(ctx, appCode)
	if err != nil {
		return itemDTO{}, err
	}
	needle := strconv.FormatInt(sellerUserID, 10)
	for _, item := range items {
		if item.SellerUserID == needle {
			return item, nil
		}
	}
	return itemDTO{}, sql.ErrNoRows
}

func (s *Service) Delete(ctx context.Context, appCode string, sellerUserID int64) error {
	if sellerUserID <= 0 {
		return errors.New("币商 ID 不正确")
	}
	if err := s.ensureSchema(ctx); err != nil {
		return err
	}
	result, err := s.walletDB.ExecContext(ctx, `DELETE FROM point_withdrawal_coin_seller_configs WHERE app_code = ? AND seller_user_id = ?`, normalizeAppCode(appCode), sellerUserID)
	if err != nil {
		return err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *Service) lookupSeller(ctx context.Context, appCode string, sellerUserID int64, requireActive bool) (displayUserID string, nickname string, avatar string, countryName string, err error) {
	if s.userDB == nil {
		return "", "", "", "", errors.New("user db is not configured")
	}
	query := `
		SELECT COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), COALESCE(u.avatar, ''),
		       COALESCE(c.country_display_name, c.country_name, u.country, '')
		FROM coin_seller_profiles csp
		JOIN users u ON u.user_id = csp.user_id AND u.app_code = csp.app_code
		LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country
		WHERE csp.app_code = ? AND csp.user_id = ?`
	if requireActive {
		query += " AND csp.status = 'active' AND u.status = 'active'"
	}
	query += " LIMIT 1"
	err = s.userDB.QueryRowContext(ctx, query, appCode, sellerUserID).Scan(&displayUserID, &nickname, &avatar, &countryName)
	if errors.Is(err, sql.ErrNoRows) && requireActive {
		return "", "", "", "", errors.New("目标用户不是当前 App 的启用币商")
	}
	return
}

func (s *Service) resolveActiveSellerUserID(ctx context.Context, appCode string, inputID int64) (int64, error) {
	if s.userDB == nil {
		return 0, errors.New("user db is not configured")
	}
	// 与后台其他用户身份入口保持一致：数字展示号优先于同值内部 ID。两次查询分别命中
	// users(app_code,current_display_user_id) 唯一索引和 users 主键，避免 OR 条件破坏索引选择。
	var sellerUserID int64
	err := s.userDB.QueryRowContext(ctx, `
		SELECT u.user_id
		FROM users u
		JOIN coin_seller_profiles csp ON csp.user_id = u.user_id AND csp.app_code = u.app_code
		WHERE u.app_code = ? AND u.current_display_user_id = ?
		  AND u.status = 'active' AND csp.status = 'active'
		LIMIT 1`, appCode, strconv.FormatInt(inputID, 10)).Scan(&sellerUserID)
	if err == nil {
		return sellerUserID, nil
	}
	if !errors.Is(err, sql.ErrNoRows) {
		return 0, err
	}

	err = s.userDB.QueryRowContext(ctx, `
		SELECT u.user_id
		FROM users u
		JOIN coin_seller_profiles csp ON csp.user_id = u.user_id AND csp.app_code = u.app_code
		WHERE u.app_code = ? AND u.user_id = ?
		  AND u.status = 'active' AND csp.status = 'active'
		LIMIT 1`, appCode, inputID).Scan(&sellerUserID)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, errors.New("目标用户不是当前 App 的启用币商")
	}
	return sellerUserID, err
}

func (s *Service) validateServiceScope(ctx context.Context, appCode string, regionIDs []int64, countryCodes []string) error {
	if s.userDB == nil {
		return errors.New("user db is not configured")
	}
	if len(regionIDs) > 0 {
		args := make([]any, 0, len(regionIDs)+1)
		args = append(args, appCode)
		for _, regionID := range regionIDs {
			args = append(args, regionID)
		}
		var count int
		query := `SELECT COUNT(*) FROM regions WHERE app_code = ? AND status = 'active' AND region_code <> 'GLOBAL' AND region_id IN (` + placeholders(len(regionIDs)) + `)`
		if err := s.userDB.QueryRowContext(ctx, query, args...).Scan(&count); err != nil {
			return err
		}
		if count != len(regionIDs) {
			return errors.New("服务区域包含不存在或已停用的区域")
		}
	}
	if len(countryCodes) > 0 {
		args := make([]any, 0, len(countryCodes)+1)
		args = append(args, appCode)
		for _, countryCode := range countryCodes {
			args = append(args, countryCode)
		}
		var count int
		query := `SELECT COUNT(*) FROM countries WHERE app_code = ? AND enabled = TRUE AND country_code IN (` + placeholders(len(countryCodes)) + `)`
		if err := s.userDB.QueryRowContext(ctx, query, args...).Scan(&count); err != nil {
			return err
		}
		if count != len(countryCodes) {
			return errors.New("服务国家包含不存在或已停用的国家")
		}
	}
	return nil
}

func placeholders(count int) string {
	if count <= 0 {
		return ""
	}
	return strings.TrimSuffix(strings.Repeat("?,", count), ",")
}

func (s *Service) ensureSchema(ctx context.Context) error {
	if s == nil || s.walletDB == nil {
		return errors.New("wallet db is not configured")
	}
	_, err := s.walletDB.ExecContext(ctx, `
		CREATE TABLE IF NOT EXISTS point_withdrawal_coin_seller_configs (
		  app_code VARCHAR(32) NOT NULL, seller_user_id BIGINT NOT NULL, sort_order INT NOT NULL DEFAULT 0,
		  point_amount BIGINT NOT NULL, seller_coin_amount BIGINT NOT NULL, service_country_codes JSON NOT NULL,
		  service_region_ids JSON NULL,
		  status VARCHAR(24) NOT NULL DEFAULT 'active', created_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
		  updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL,
		  PRIMARY KEY (app_code, seller_user_id),
		  KEY idx_point_withdraw_seller_list (app_code, status, sort_order, seller_user_id)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='POINT 提现币商白名单与兑换比例'`)
	if err != nil {
		return err
	}
	var columnCount int
	if err := s.walletDB.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM information_schema.columns
		WHERE table_schema = DATABASE() AND table_name = 'point_withdrawal_coin_seller_configs'
		  AND column_name = 'service_region_ids'`).Scan(&columnCount); err != nil {
		return err
	}
	if columnCount > 0 {
		return nil
	}
	// 这是按币商唯一的小型配置表；INSTANT 加 nullable JSON 列不复制表，也不回填历史数据。
	_, err = s.walletDB.ExecContext(ctx, `
		ALTER TABLE point_withdrawal_coin_seller_configs
		ADD COLUMN service_region_ids JSON NULL AFTER service_country_codes,
		ALGORITHM=INSTANT`)
	return err
}

func normalizeCountries(values []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.ToUpper(strings.TrimSpace(value))
		if value == "" || len(value) > 16 {
			continue
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	sort.Strings(out)
	return out
}

func normalizeRegionIDs(values []int64) []int64 {
	seen := map[int64]struct{}{}
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func normalizeAppCode(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	if value == "" {
		return "lalu"
	}
	return value
}
