package mysql

import (
	"context"
	"database/sql"
	"errors"
	"sort"
	"strings"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"

	mysqlDriver "github.com/go-sql-driver/mysql"
)

// ListRechargeProducts 返回 App 当前区域和平台可购买的内购商品；只读取已上架配置。
func (r *Repository) ListRechargeProducts(ctx context.Context, _ int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) {
	if r == nil || r.db == nil {
		return nil, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	app := appcode.FromContext(ctx)
	platform = ledger.NormalizeRechargeProductPlatform(platform)
	audienceType = ledger.NormalizeRechargeAudienceType(audienceType)
	where := `
		WHERE p.app_code = ? AND p.status = ? AND p.app_visible = TRUE AND p.audience_type = ? AND pr.region_id = ?`
	args := []any{app, ledger.RechargeProductStatusActive, audienceType, regionID}
	if platform != "" {
		where += ` AND p.platform = ?`
		args = append(args, platform)
	}
	rows, err := r.db.QueryContext(ctx, rechargeProductSelectSQL()+`
		JOIN wallet_recharge_product_regions pr
		  ON pr.app_code = p.app_code AND pr.product_id = p.product_id
		`+where+`
		ORDER BY p.amount_micro ASC, p.product_id ASC`, args...)
	if err != nil {
		return nil, nil, err
	}
	defer rows.Close()

	products := make([]ledger.RechargeProduct, 0)
	channels := make([]string, 0, 2)
	seenChannels := map[string]struct{}{}
	for rows.Next() {
		product, err := scanRechargeProduct(rows)
		if err != nil {
			return nil, nil, err
		}
		product.RegionIDs = []int64{regionID}
		products = append(products, product)
		if product.Channel != "" {
			if _, ok := seenChannels[product.Channel]; !ok {
				seenChannels[product.Channel] = struct{}{}
				channels = append(channels, product.Channel)
			}
		}
	}
	return products, channels, rows.Err()
}

// ListAdminRechargeProducts 返回后台内购配置分页列表，并带出支持区域集合。
func (r *Repository) ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error) {
	if r == nil || r.db == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, query.AppCode)
	query.AppCode = appcode.FromContext(ctx)
	query = normalizeRechargeProductQuery(query)
	where, args := rechargeProductWhereSQL(query)
	total, err := r.countRechargeProducts(ctx, where, args...)
	if err != nil {
		return nil, 0, err
	}
	rows, err := r.db.QueryContext(ctx, rechargeProductSelectSQL()+`
		`+where+`
		ORDER BY p.product_id DESC
		LIMIT ? OFFSET ?`,
		append(args, query.PageSize, resourceOffset(query.Page, query.PageSize))...,
	)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	products := make([]ledger.RechargeProduct, 0, query.PageSize)
	for rows.Next() {
		product, err := scanRechargeProduct(rows)
		if err != nil {
			return nil, 0, err
		}
		products = append(products, product)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	if err := r.attachRechargeProductRegions(ctx, r.db, query.AppCode, products); err != nil {
		return nil, 0, err
	}
	return products, total, nil
}

// CreateRechargeProduct 写入内购商品和区域映射，product_code 和 Google 商品 ID 保持一致。
func (r *Repository) CreateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) {
	if r == nil || r.db == nil {
		return ledger.RechargeProduct{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command = normalizeRechargeProductCommand(command)
	command.AppCode = appcode.FromContext(ctx)

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return ledger.RechargeProduct{}, err
	}
	defer func() { _ = tx.Rollback() }()

	nowMs := time.Now().UnixMilli()
	result, err := tx.ExecContext(ctx, `
		INSERT INTO wallet_recharge_products (
		   app_code, audience_type, product_name, description, platform, channel, currency_code, amount_micro,
		   coin_amount, resource_asset_type, status, app_visible, sort_order, created_by_user_id, updated_by_user_id,
		   created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		command.AppCode, command.AudienceType, command.ProductName, command.Description, command.Platform,
		ledger.RechargeChannelForPlatform(command.Platform), ledger.PaidCurrencyUSDT, command.AmountMicro,
		command.CoinAmount, rechargeProductAssetType(command.AudienceType), productStatus(command.Enabled), rechargeProductAppVisible(command, true), 0,
		command.OperatorUserID, command.OperatorUserID, nowMs, nowMs,
	)
	if err != nil {
		return ledger.RechargeProduct{}, mapRechargeProductWriteError(err)
	}
	productID, err := result.LastInsertId()
	if err != nil {
		return ledger.RechargeProduct{}, err
	}
	productCode := command.ProductName
	if _, err := tx.ExecContext(ctx, `
		UPDATE wallet_recharge_products
		SET product_code = ?
		WHERE app_code = ? AND product_id = ?`,
		productCode, command.AppCode, productID,
	); err != nil {
		return ledger.RechargeProduct{}, mapRechargeProductWriteError(err)
	}
	if err := replaceRechargeProductRegions(ctx, tx, command.AppCode, productID, command.RegionIDs, nowMs); err != nil {
		return ledger.RechargeProduct{}, err
	}
	if err := tx.Commit(); err != nil {
		return ledger.RechargeProduct{}, err
	}
	return r.GetRechargeProduct(ctx, command.AppCode, productID)
}

// UpdateRechargeProduct 覆盖商品展示、价格、平台、上架状态和区域映射，并同步商品编码。
func (r *Repository) UpdateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) {
	if r == nil || r.db == nil {
		return ledger.RechargeProduct{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command = normalizeRechargeProductCommand(command)
	command.AppCode = appcode.FromContext(ctx)

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return ledger.RechargeProduct{}, err
	}
	defer func() { _ = tx.Rollback() }()

	nowMs := time.Now().UnixMilli()
	result, err := tx.ExecContext(ctx, `
		UPDATE wallet_recharge_products
		SET audience_type = ?, product_code = ?, product_name = ?, description = ?, platform = ?, channel = ?, currency_code = ?,
		    amount_micro = ?, coin_amount = ?, resource_asset_type = ?, status = ?,
		    app_visible = CASE WHEN ? THEN ? ELSE app_visible END,
		    updated_by_user_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND product_id = ?`,
		command.AudienceType, command.ProductName, command.ProductName, command.Description, command.Platform, ledger.RechargeChannelForPlatform(command.Platform),
		ledger.PaidCurrencyUSDT, command.AmountMicro, command.CoinAmount, rechargeProductAssetType(command.AudienceType), productStatus(command.Enabled),
		command.AppVisible != nil, rechargeProductAppVisible(command, true),
		command.OperatorUserID, nowMs, command.AppCode, command.ProductID,
	)
	if err != nil {
		return ledger.RechargeProduct{}, mapRechargeProductWriteError(err)
	}
	if affected, err := result.RowsAffected(); err != nil {
		return ledger.RechargeProduct{}, err
	} else if affected == 0 {
		var matched int
		err := tx.QueryRowContext(ctx, `
			SELECT 1
			FROM wallet_recharge_products
			WHERE app_code = ? AND product_id = ?`,
			command.AppCode, command.ProductID,
		).Scan(&matched)
		if errors.Is(err, sql.ErrNoRows) {
			return ledger.RechargeProduct{}, xerr.New(xerr.NotFound, "recharge product not found")
		}
		if err != nil {
			return ledger.RechargeProduct{}, err
		}
	}
	if err := replaceRechargeProductRegions(ctx, tx, command.AppCode, command.ProductID, command.RegionIDs, nowMs); err != nil {
		return ledger.RechargeProduct{}, err
	}
	if err := tx.Commit(); err != nil {
		return ledger.RechargeProduct{}, err
	}
	return r.GetRechargeProduct(ctx, command.AppCode, command.ProductID)
}

// DeleteRechargeProduct 删除商品配置和区域映射；当前 provider 订单未落表，因此不保留软删除历史。
func (r *Repository) DeleteRechargeProduct(ctx context.Context, appCode string, productID int64) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, appCode)
	appCode = appcode.FromContext(ctx)
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()
	if _, err := tx.ExecContext(ctx, `
		DELETE FROM wallet_recharge_product_regions
		WHERE app_code = ? AND product_id = ?`,
		appCode, productID,
	); err != nil {
		return err
	}
	result, err := tx.ExecContext(ctx, `
		DELETE FROM wallet_recharge_products
		WHERE app_code = ? AND product_id = ?`,
		appCode, productID,
	)
	if err != nil {
		return err
	}
	if affected, err := result.RowsAffected(); err != nil {
		return err
	} else if affected == 0 {
		return xerr.New(xerr.NotFound, "recharge product not found")
	}
	return tx.Commit()
}

func (r *Repository) GetRechargeProduct(ctx context.Context, appCode string, productID int64) (ledger.RechargeProduct, error) {
	ctx = contextWithCommandApp(ctx, appCode)
	appCode = appcode.FromContext(ctx)
	product, err := scanRechargeProduct(r.db.QueryRowContext(ctx, rechargeProductSelectSQL()+`
		WHERE p.app_code = ? AND p.product_id = ?`,
		appCode, productID,
	))
	if errors.Is(err, sql.ErrNoRows) {
		return ledger.RechargeProduct{}, xerr.New(xerr.NotFound, "recharge product not found")
	}
	if err != nil {
		return ledger.RechargeProduct{}, err
	}
	products := []ledger.RechargeProduct{product}
	if err := r.attachRechargeProductRegions(ctx, r.db, appCode, products); err != nil {
		return ledger.RechargeProduct{}, err
	}
	return products[0], nil
}

func (r *Repository) GetGoogleRechargeProductByCode(ctx context.Context, appCode string, googleProductID string) (ledger.RechargeProduct, error) {
	ctx = contextWithCommandApp(ctx, appCode)
	appCode = appcode.FromContext(ctx)
	googleProductID = strings.TrimSpace(googleProductID)
	if googleProductID == "" {
		return ledger.RechargeProduct{}, xerr.New(xerr.InvalidArgument, "google_product_id is required")
	}
	// 首充确认没有 App 充值页的 product_id，只能用 Google 商品 ID 回查内部商品；
	// product_name 是当前 Google 商品 ID，product_code 是历史兼容快照，两者都必须精确匹配。
	product, err := scanRechargeProduct(r.db.QueryRowContext(ctx, rechargeProductSelectSQL()+`
		WHERE p.app_code = ?
		  AND p.platform = ?
		  AND p.channel = ?
		  AND (p.product_name = ? OR p.product_code = ?)
		ORDER BY CASE WHEN p.product_name = ? THEN 0 ELSE 1 END, p.product_id DESC
		LIMIT 1`,
		appCode,
		ledger.RechargeProductPlatformAndroid,
		ledger.RechargeChannelGoogle,
		googleProductID,
		googleProductID,
		googleProductID,
	))
	if errors.Is(err, sql.ErrNoRows) {
		return ledger.RechargeProduct{}, xerr.New(xerr.NotFound, "recharge product not found")
	}
	if err != nil {
		return ledger.RechargeProduct{}, err
	}
	products := []ledger.RechargeProduct{product}
	if err := r.attachRechargeProductRegions(ctx, r.db, appCode, products); err != nil {
		return ledger.RechargeProduct{}, err
	}
	return products[0], nil
}

func rechargeProductSelectSQL() string {
	return `
		SELECT p.app_code, p.product_id, COALESCE(p.product_code, ''), p.product_name,
		       p.description, p.platform, p.channel, p.currency_code, p.amount_micro,
		       p.coin_amount, p.resource_asset_type, p.status, COALESCE(p.app_visible, TRUE), p.sort_order,
		       p.created_by_user_id, p.updated_by_user_id, p.created_at_ms, p.updated_at_ms,
		       COALESCE(p.audience_type, 'normal')
		FROM wallet_recharge_products p
	`
}

func scanRechargeProduct(scanner scanTarget) (ledger.RechargeProduct, error) {
	var product ledger.RechargeProduct
	var createdBy sql.NullInt64
	var updatedBy sql.NullInt64
	if err := scanner.Scan(
		&product.AppCode,
		&product.ProductID,
		&product.ProductCode,
		&product.ProductName,
		&product.Description,
		&product.Platform,
		&product.Channel,
		&product.CurrencyCode,
		&product.AmountMicro,
		&product.CoinAmount,
		&product.ResourceAssetType,
		&product.Status,
		&product.AppVisible,
		&product.SortOrder,
		&createdBy,
		&updatedBy,
		&product.CreatedAtMS,
		&product.UpdatedAtMS,
		&product.AudienceType,
	); err != nil {
		return ledger.RechargeProduct{}, err
	}
	if product.ProductCode == "" {
		product.ProductCode = product.ProductName
	}
	if product.Channel == "" {
		product.Channel = ledger.RechargeChannelForPlatform(product.Platform)
	}
	product.AmountMinor = product.AmountMicro
	product.Enabled = product.Status == ledger.RechargeProductStatusActive
	product.AudienceType = ledger.NormalizeRechargeAudienceType(product.AudienceType)
	if createdBy.Valid {
		product.CreatedByUserID = createdBy.Int64
	}
	if updatedBy.Valid {
		product.UpdatedByUserID = updatedBy.Int64
	}
	return product, nil
}

func (r *Repository) attachRechargeProductRegions(ctx context.Context, q sqlQuerier, appCode string, products []ledger.RechargeProduct) error {
	if len(products) == 0 {
		return nil
	}
	productIDs := make([]any, 0, len(products))
	productByID := make(map[int64]*ledger.RechargeProduct, len(products))
	for index := range products {
		productIDs = append(productIDs, products[index].ProductID)
		productByID[products[index].ProductID] = &products[index]
	}
	placeholders := strings.TrimSuffix(strings.Repeat("?,", len(productIDs)), ",")
	args := append([]any{appCode}, productIDs...)
	rows, err := q.QueryContext(ctx, `
		SELECT product_id, region_id
		FROM wallet_recharge_product_regions
		WHERE app_code = ? AND product_id IN (`+placeholders+`)
		ORDER BY product_id ASC, region_id ASC`, args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var productID int64
		var regionID int64
		if err := rows.Scan(&productID, &regionID); err != nil {
			return err
		}
		if product := productByID[productID]; product != nil {
			product.RegionIDs = append(product.RegionIDs, regionID)
		}
	}
	return rows.Err()
}

func replaceRechargeProductRegions(ctx context.Context, tx *sql.Tx, appCode string, productID int64, regionIDs []int64, nowMs int64) error {
	if _, err := tx.ExecContext(ctx, `
		DELETE FROM wallet_recharge_product_regions
		WHERE app_code = ? AND product_id = ?`,
		appCode, productID,
	); err != nil {
		return err
	}
	for _, regionID := range regionIDs {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO wallet_recharge_product_regions (app_code, product_id, region_id, created_at_ms)
			VALUES (?, ?, ?, ?)`,
			appCode, productID, regionID, nowMs,
		); err != nil {
			return err
		}
	}
	return nil
}

func normalizeRechargeProductQuery(query ledger.ListRechargeProductsQuery) ledger.ListRechargeProductsQuery {
	query.Status = ledger.NormalizeRechargeProductStatus(query.Status)
	query.Platform = ledger.NormalizeRechargeProductPlatform(query.Platform)
	// 后台列表的空 audience_type 表示“全部用户类型”，不能复用前台充值入口的默认 normal 语义；
	// 否则 admin-platform 选择“全部用户类型/全部平台”时会被静默收窄成普通用户，币商 Web 档位不会出现。
	if strings.TrimSpace(query.AudienceType) != "" {
		query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType)
	}
	query.Keyword = strings.TrimSpace(query.Keyword)
	query.Page, query.PageSize = normalizePage(query.Page, query.PageSize)
	return query
}

func rechargeProductWhereSQL(query ledger.ListRechargeProductsQuery) (string, []any) {
	where := `WHERE p.app_code = ?`
	args := []any{query.AppCode}
	if query.Status != "" {
		where += ` AND p.status = ?`
		args = append(args, query.Status)
	}
	if query.Platform != "" {
		where += ` AND p.platform = ?`
		args = append(args, query.Platform)
	}
	if query.AudienceType != "" {
		where += ` AND p.audience_type = ?`
		args = append(args, query.AudienceType)
	}
	if query.RegionID > 0 {
		where += ` AND EXISTS (
			SELECT 1 FROM wallet_recharge_product_regions pr
			WHERE pr.app_code = p.app_code AND pr.product_id = p.product_id AND pr.region_id = ?
		)`
		args = append(args, query.RegionID)
	}
	if query.Keyword != "" {
		like := "%" + query.Keyword + "%"
		where += ` AND (p.product_code LIKE ? OR p.product_name LIKE ? OR p.description LIKE ?)`
		args = append(args, like, like, like)
	}
	return where, args
}

func (r *Repository) countRechargeProducts(ctx context.Context, where string, args ...any) (int64, error) {
	var total int64
	err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM wallet_recharge_products p
		`+where,
		args...,
	).Scan(&total)
	return total, err
}

func normalizeRechargeProductCommand(command ledger.RechargeProductCommand) ledger.RechargeProductCommand {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.ProductName = strings.TrimSpace(command.ProductName)
	command.Description = strings.TrimSpace(command.Description)
	command.Platform = ledger.NormalizeRechargeProductPlatform(command.Platform)
	command.AudienceType = ledger.NormalizeRechargeAudienceType(command.AudienceType)
	command.RegionIDs = normalizeRechargeProductRegions(command.RegionIDs)
	return command
}

func rechargeProductAppVisible(command ledger.RechargeProductCommand, defaultValue bool) bool {
	if command.AppVisible == nil {
		return defaultValue
	}
	return *command.AppVisible
}

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

func productStatus(enabled bool) string {
	if enabled {
		return ledger.RechargeProductStatusActive
	}
	return ledger.RechargeProductStatusDisabled
}

func rechargeProductAssetType(audienceType string) string {
	if ledger.NormalizeRechargeAudienceType(audienceType) == ledger.RechargeAudienceCoinSeller {
		return ledger.AssetCoinSellerCoin
	}
	return ledger.AssetCoin
}

func mapRechargeProductWriteError(err error) error {
	var mysqlErr *mysqlDriver.MySQLError
	if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
		return xerr.New(xerr.Conflict, "recharge product already exists")
	}
	return err
}
