// 本包承载 user-service 用户主数据的 MySQL 持久化。
package user

import (
	"context"
	"database/sql"
	"strings"

	"hyapp/pkg/appcode"
	userdomain "hyapp/services/user-service/internal/domain/user"
	"hyapp/services/user-service/internal/storage/mysql/shared"
)

// Repository 是用户主数据在 MySQL 上的存储实现。
type Repository struct {
	// db 由父级 MySQL repository 持有，本包只复用连接池。
	db *sql.DB
}

// New 基于共享 MySQL 连接池创建用户主数据存储对象。
func New(db *sql.DB) *Repository {
	return &Repository{db: db}
}

const userSelectColumns = `
		app_code,
		user_id,
		default_display_user_id,
		current_display_user_id,
		current_display_user_id_kind,
		COALESCE(current_display_user_id_expires_at_ms, 0),
		COALESCE((
			SELECT pdi.pretty_id
			FROM pretty_display_user_id_leases pdl
			LEFT JOIN pretty_display_ids pdi
			  ON pdi.app_code = pdl.app_code
			 AND pdi.assigned_lease_id = pdl.lease_id
			 AND pdi.assigned_user_id = pdl.user_id
			WHERE pdl.app_code = users.app_code
			  AND pdl.user_id = users.user_id
			  AND pdl.status = 'active'
			ORDER BY pdl.starts_at_ms DESC
			LIMIT 1
		), ''),
		COALESCE((
			SELECT pdl.display_user_id
			FROM pretty_display_user_id_leases pdl
			WHERE pdl.app_code = users.app_code
			  AND pdl.user_id = users.user_id
			  AND pdl.status = 'active'
			ORDER BY pdl.starts_at_ms DESC
			LIMIT 1
		), ''),
		COALESCE(username, ''),
		COALESCE(gender, ''),
		COALESCE(country, ''),
		CASE
			WHEN COALESCE(region_id, 0) = 0 THEN 0
			WHEN EXISTS (
				SELECT 1
				FROM regions rg
				WHERE rg.app_code = users.app_code
				  AND rg.region_id = users.region_id
				  AND rg.status = 'active'
				  AND rg.region_code NOT IN ('Australia and New Zealand', 'Caribbean', 'Melanesia', 'Micronesia', 'Polynesia', 'Southern Africa', 'UNSPECIFIED')
				LIMIT 1
			) THEN users.region_id
			ELSE 0
		END,
		COALESCE((SELECT c.country_id FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), 0),
		COALESCE((SELECT c.country_name FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), ''),
		COALESCE((SELECT c.country_display_name FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), ''),
		COALESCE((SELECT c.iso_numeric FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), ''),
		COALESCE((SELECT c.phone_country_code FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), ''),
		COALESCE((SELECT c.enabled FROM countries c WHERE c.app_code = users.app_code AND c.country_code = users.country LIMIT 1), 0),
		CASE
			WHEN COALESCE(users.region_id, 0) = 0 OR NOT EXISTS (
				SELECT 1
				FROM regions rg
				WHERE rg.app_code = users.app_code
				  AND rg.region_id = users.region_id
				  AND rg.status = 'active'
				  AND rg.region_code NOT IN ('Australia and New Zealand', 'Caribbean', 'Melanesia', 'Micronesia', 'Polynesia', 'Southern Africa', 'UNSPECIFIED')
				LIMIT 1
			) THEN 'GLOBAL'
			ELSE COALESCE((SELECT rg.region_code FROM regions rg WHERE rg.app_code = users.app_code AND rg.region_id = users.region_id LIMIT 1), '')
		END,
		CASE
			WHEN COALESCE(users.region_id, 0) = 0 OR NOT EXISTS (
				SELECT 1
				FROM regions rg
				WHERE rg.app_code = users.app_code
				  AND rg.region_id = users.region_id
				  AND rg.status = 'active'
				  AND rg.region_code NOT IN ('Australia and New Zealand', 'Caribbean', 'Melanesia', 'Micronesia', 'Polynesia', 'Southern Africa', 'UNSPECIFIED')
				LIMIT 1
			) THEN 'GLOBAL'
			ELSE COALESCE((SELECT rg.name FROM regions rg WHERE rg.app_code = users.app_code AND rg.region_id = users.region_id LIMIT 1), '')
		END,
		COALESCE(invite_code, ''),
		COALESCE(register_ip, ''),
		COALESCE(register_user_agent, ''),
		COALESCE(country_by_ip, ''),
		COALESCE(register_device_id, ''),
		COALESCE(register_device, ''),
		COALESCE(os_version, ''),
		COALESCE(avatar, ''),
		COALESCE(profile_bg_img, ''),
		COALESCE(contact_info, ''),
		COALESCE(withdraw_usdt_trc20_address, ''),
		COALESCE(DATE_FORMAT(birth_date, '%Y-%m-%d'), ''),
		COALESCE(app_version, ''),
		COALESCE(build_number, ''),
		COALESCE(source, ''),
		COALESCE(install_channel, ''),
		COALESCE(campaign, ''),
		COALESCE(platform, ''),
		COALESCE(language, ''),
		COALESCE(timezone, ''),
		COALESCE(profile_completed, 0),
		COALESCE(profile_completed_at_ms, 0),
		COALESCE(onboarding_status, 'profile_required'),
		status,
		created_at_ms,
		updated_at_ms`

func QueryUser(ctx context.Context, q shared.Queryer, clause string, args ...any) (userdomain.User, error) {
	// clause 由本包内部固定拼接，不能接受外部原始 SQL。
	clause, args = withUserAppClause(ctx, clause, args...)
	row := q.QueryRowContext(ctx, `
		SELECT `+userSelectColumns+`
		FROM users
		`+clause, args...)

	return ScanUser(row)
}

// ScanUser 固定 users 投影到领域模型的转换。

func ScanUser(scanner interface {
	Scan(dest ...any) error
}) (userdomain.User, error) {
	// scanUser 是 users 表到领域模型的唯一转换点。
	var user userdomain.User
	var status string
	var kind string
	var onboardingStatus string
	err := scanner.Scan(
		&user.AppCode,
		&user.UserID,
		&user.DefaultDisplayUserID,
		&user.CurrentDisplayUserID,
		&kind,
		&user.CurrentDisplayUserIDExpiresAtMs,
		&user.PrettyID,
		&user.PrettyDisplayUserID,
		&user.Username,
		&user.Gender,
		&user.Country,
		&user.RegionID,
		&user.CountryID,
		&user.CountryName,
		&user.CountryDisplayName,
		&user.ISONumeric,
		&user.PhoneCountryCode,
		&user.CountryEnabled,
		&user.RegionCode,
		&user.RegionName,
		&user.InviteCode,
		&user.RegisterIP,
		&user.RegisterUserAgent,
		&user.CountryByIP,
		&user.RegisterDeviceID,
		&user.RegisterDevice,
		&user.RegisterOSVersion,
		&user.Avatar,
		&user.ProfileBgImg,
		&user.ContactInfo,
		&user.WithdrawUSDTTRC20Address,
		&user.BirthDate,
		&user.RegisterAppVersion,
		&user.RegisterBuildNumber,
		&user.RegisterSource,
		&user.RegisterInstallChannel,
		&user.RegisterCampaign,
		&user.RegisterPlatform,
		&user.Language,
		&user.Timezone,
		&user.ProfileCompleted,
		&user.ProfileCompletedAtMs,
		&onboardingStatus,
		&status,
		&user.CreatedAtMs,
		&user.UpdatedAtMs,
	)
	if err != nil {
		return userdomain.User{}, err
	}

	user.Status = userdomain.Status(status)
	user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKind(kind)
	user.OnboardingStatus = userdomain.OnboardingStatus(onboardingStatus)
	if user.OnboardingStatus == "" {
		if user.ProfileCompleted {
			user.OnboardingStatus = userdomain.OnboardingStatusCompleted
		} else {
			user.OnboardingStatus = userdomain.OnboardingStatusProfileRequired
		}
	}
	return user, nil
}

// InsertUserIdentity 是跨认证/用户创建用户用例共享的事务片段。
// 它只在调用方已经开启事务后执行，避免业务层手动拼跨表事务。

func InsertUserIdentity(ctx context.Context, tx *sql.Tx, user userdomain.User, identity userdomain.Identity) error {
	// 调用方已开启事务，本函数只负责插入 users 和默认 active display_user_id。
	if user.DefaultDisplayUserID == "" {
		// 创建路径允许 user 中未预填默认短号，以同事务创建的 identity 为准。
		user.DefaultDisplayUserID = identity.DisplayUserID
	}
	if user.CurrentDisplayUserID == "" {
		user.CurrentDisplayUserID = identity.DisplayUserID
	}
	if user.CurrentDisplayUserIDKind == "" {
		// 新用户默认短号永远是 default kind。
		user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKindDefault
	}
	// profile_bg_img 是非空列；新建账号没有背景图时必须写空字符串，不能复用 NullableString 写成 SQL NULL。
	profileBgImg := strings.TrimSpace(user.ProfileBgImg)

	_, err := tx.ExecContext(ctx, `
		INSERT INTO users (
			app_code,
			user_id,
			default_display_user_id,
			current_display_user_id,
			current_display_user_id_kind,
			current_display_user_id_expires_at_ms,
			username,
			gender,
			country,
			region_id,
			invite_code,
			register_ip,
			register_user_agent,
			country_by_ip,
			register_device_id,
			register_device,
			os_version,
			avatar,
			profile_bg_img,
			contact_info,
			withdraw_usdt_trc20_address,
			birth_date,
			app_version,
			build_number,
			source,
			install_channel,
			campaign,
			platform,
			language,
			timezone,
			profile_completed,
			profile_completed_at_ms,
			onboarding_status,
			status,
			created_at_ms,
			updated_at_ms
		)
		VALUES (
			?, ?, ?, ?, ?, NULL,
			?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
			?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
			?, ?, ?, ?, ?, ?, ?, ?, ?, ?
		)
	`, appcode.Normalize(user.AppCode), user.UserID, user.DefaultDisplayUserID, user.CurrentDisplayUserID, string(user.CurrentDisplayUserIDKind), shared.NullableString(user.Username), shared.NullableString(user.Gender), shared.NullableString(user.Country), shared.NullableRegionID(user.RegionID), shared.NullableString(user.InviteCode), shared.NullableString(user.RegisterIP), shared.NullableString(user.RegisterUserAgent), shared.NullableString(user.CountryByIP), shared.NullableString(user.RegisterDeviceID), shared.NullableString(user.RegisterDevice), shared.NullableString(user.RegisterOSVersion), shared.NullableString(user.Avatar), profileBgImg, strings.TrimSpace(user.ContactInfo), strings.TrimSpace(user.WithdrawUSDTTRC20Address), shared.NullableString(user.BirthDate), shared.NullableString(user.RegisterAppVersion), shared.NullableString(user.RegisterBuildNumber), shared.NullableString(user.RegisterSource), shared.NullableString(user.RegisterInstallChannel), shared.NullableString(user.RegisterCampaign), shared.NullableString(user.RegisterPlatform), shared.NullableString(user.Language), shared.NullableString(user.Timezone), user.ProfileCompleted, user.ProfileCompletedAtMs, onboardingStatusForInsert(user), string(user.Status), user.CreatedAtMs, user.UpdatedAtMs)
	if err != nil {
		return err
	}

	_, err = tx.ExecContext(ctx, `
		INSERT INTO user_display_user_ids (app_code, display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(user.AppCode), identity.DisplayUserID, user.UserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive), user.CreatedAtMs, user.CreatedAtMs, user.CreatedAtMs, user.UpdatedAtMs)
	// user_display_user_ids 唯一约束保证默认短号不和 active/held/reserved/blocked 记录冲突。
	return err
}

func onboardingStatusForInsert(user userdomain.User) string {
	// 创建路径默认保持未完成状态；只有 CompleteOnboarding 能把状态固化为 completed。
	if user.OnboardingStatus != "" {
		return string(user.OnboardingStatus)
	}
	if user.ProfileCompleted {
		return string(userdomain.OnboardingStatusCompleted)
	}

	return string(userdomain.OnboardingStatusProfileRequired)
}

func latestCountryChangedAt(ctx context.Context, tx *sql.Tx, userID int64) (int64, error) {
	// users 行已 FOR UPDATE；按同一用户串行化后读取最新日志即可防止并发绕过冷却。
	var changedAtMs int64
	err := tx.QueryRowContext(ctx, `
		SELECT changed_at_ms
		FROM user_country_change_logs
		WHERE app_code = ? AND user_id = ?
		ORDER BY changed_at_ms DESC
		LIMIT 1
	`, appcode.FromContext(ctx), userID).Scan(&changedAtMs)
	if err == sql.ErrNoRows {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}

	return changedAtMs, nil
}

func withUserAppClause(ctx context.Context, clause string, args ...any) (string, []any) {
	appCode := appcode.FromContext(ctx)
	trimmed := strings.TrimSpace(clause)
	if strings.HasPrefix(strings.ToUpper(trimmed), "WHERE ") {
		withoutWhere := strings.TrimSpace(trimmed[len("WHERE "):])
		return "WHERE users.app_code = ? AND " + withoutWhere, append([]any{appCode}, args...)
	}
	if trimmed == "" {
		return "WHERE users.app_code = ?", []any{appCode}
	}

	return trimmed, args
}
