package auth

import (
	"context"
	"database/sql"
	"fmt"
	"hash/fnv"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
)

const (
	displayUserIDMin              int64  = 1000000
	displayUserIDSpace            int64  = 9000000
	displayUserIDPermutationBase  uint32 = 3000
	displayUserIDPermutationRound        = 7
	displayUserIDAllocateAttempts        = 4096
)

var displayUserIDPermutationKeys = [displayUserIDPermutationRound]uint32{
	0x243f6a88,
	0x85a308d3,
	0x13198a2e,
	0x03707344,
	0xa4093822,
	0x299f31d0,
	0x082efa98,
}

type displayUserIDQueryer interface {
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

// AllocateDisplayUserID 为当前 App 分配 7 位随机外观默认短号。
// 数据库只保存递增内部序号；对外短号由 3000x3000 Feistel 置换得到，所以同一 App 内序号不重复就不会产出重复短号。
// 旧库里可能已有随机号或人工种子号，因此分配时仍用唯一索引点查跳过已占用值，最终创建事务再用唯一键兜住并发窗口。
func (r *Repository) AllocateDisplayUserID(ctx context.Context, appCode string) (string, error) {
	normalized := appcode.Normalize(appCode)
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return "", err
	}
	defer tx.Rollback()

	nowMs := time.Now().UTC().UnixMilli()
	nextValue, err := r.lockDisplayUserIDSequence(ctx, tx, normalized, nowMs)
	if err != nil {
		return "", err
	}

	for attempt := 0; attempt < displayUserIDAllocateAttempts; attempt++ {
		if nextValue < 0 || nextValue >= displayUserIDSpace {
			return "", xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id space exhausted")
		}
		candidate, err := displayUserIDFromOrdinal(normalized, nextValue)
		if err != nil {
			return "", err
		}
		occupied, err := displayUserIDOccupied(ctx, tx, normalized, candidate)
		if err != nil {
			return "", err
		}
		nextValue++
		if occupied {
			// 历史随机号、测试种子号或人工保留号可能已经占住当前置换结果；推进内部序号继续找下一个可用值。
			continue
		}
		if _, err := tx.ExecContext(ctx, `
			UPDATE display_user_id_sequences
			SET next_value = ?, updated_at_ms = ?
			WHERE app_code = ?
		`, nextValue, nowMs, normalized); err != nil {
			return "", err
		}
		if err := tx.Commit(); err != nil {
			return "", err
		}

		return candidate, nil
	}

	return "", xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id allocation failed")
}

func (r *Repository) lockDisplayUserIDSequence(ctx context.Context, tx *sql.Tx, appCode string, nowMs int64) (int64, error) {
	var nextValue int64
	err := tx.QueryRowContext(ctx, `
		SELECT next_value
		FROM display_user_id_sequences
		WHERE app_code = ?
		FOR UPDATE
	`, appCode).Scan(&nextValue)
	if err == nil {
		return nextValue, nil
	}
	if err != sql.ErrNoRows {
		return 0, err
	}

	initialValue, err := initialDisplayUserIDSequenceValue(ctx, tx, appCode)
	if err != nil {
		return 0, err
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT IGNORE INTO display_user_id_sequences (app_code, next_value, updated_at_ms)
		VALUES (?, ?, ?)
	`, appCode, initialValue, nowMs); err != nil {
		return 0, err
	}
	if err := tx.QueryRowContext(ctx, `
		SELECT next_value
		FROM display_user_id_sequences
		WHERE app_code = ?
		FOR UPDATE
	`, appCode).Scan(&nextValue); err != nil {
		return 0, err
	}

	return nextValue, nil
}

func initialDisplayUserIDSequenceValue(ctx context.Context, queryer displayUserIDQueryer, appCode string) (int64, error) {
	// 老库补表时用现有占用量作为内部序号起点；这不是唯一性来源，只是减少迁移后从 0 开始撞历史号的概率。
	var initialValue int64
	err := queryer.QueryRowContext(ctx, `
		SELECT GREATEST(
			(SELECT COUNT(*) FROM user_display_user_ids WHERE app_code = ? AND live_display_user_id IS NOT NULL),
			(SELECT COUNT(*) FROM users WHERE app_code = ?)
		)
	`, appCode, appCode).Scan(&initialValue)
	if err != nil {
		return 0, err
	}

	return initialValue, nil
}

func displayUserIDFromOrdinal(appCode string, ordinal int64) (string, error) {
	if ordinal < 0 || ordinal >= displayUserIDSpace {
		return "", xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id space exhausted")
	}

	return fmt.Sprintf("%07d", displayUserIDMin+int64(permuteDisplayUserIDOrdinal(appCode, uint32(ordinal)))), nil
}

func permuteDisplayUserIDOrdinal(appCode string, ordinal uint32) uint32 {
	// 7 位账号空间是 9,000,000 = 3000 * 3000；把内部序号拆成两个 3000 进制半区后做 Feistel 置换。
	// 每一轮都是可逆映射，因此整个函数是 0..8,999,999 上的一一置换，不会像随机取号一样产生生日碰撞。
	left := ordinal / displayUserIDPermutationBase
	right := ordinal % displayUserIDPermutationBase
	appHash := displayUserIDAppHash(appCode)
	for round := 0; round < displayUserIDPermutationRound; round++ {
		nextLeft := right
		nextRight := (left + displayUserIDRoundValue(right, appHash, round)) % displayUserIDPermutationBase
		left, right = nextLeft, nextRight
	}

	return left*displayUserIDPermutationBase + right
}

func displayUserIDRoundValue(right uint32, appHash uint32, round int) uint32 {
	// 轮函数只需要伪随机分布，不需要可逆；Feistel 结构负责整体可逆性。
	x := right + appHash + displayUserIDPermutationKeys[round] + uint32(round)*0x9e3779b9
	x ^= x >> 15
	x *= 0x2c1b3c6d
	x ^= x >> 12
	x *= 0x297a2d39
	x ^= x >> 15
	return x % displayUserIDPermutationBase
}

func displayUserIDAppHash(appCode string) uint32 {
	hash := fnv.New32a()
	_, _ = hash.Write([]byte(appcode.Normalize(appCode)))
	return hash.Sum32()
}

func displayUserIDOccupied(ctx context.Context, queryer displayUserIDQueryer, appCode string, displayUserID string) (bool, error) {
	var exists int
	err := queryer.QueryRowContext(ctx, `
		SELECT 1
		FROM (
			SELECT 1
			FROM user_display_user_ids
			WHERE app_code = ? AND live_display_user_id = ?
			UNION ALL
			SELECT 1
			FROM users
			WHERE app_code = ? AND default_display_user_id = ?
			UNION ALL
			SELECT 1
			FROM users
			WHERE app_code = ? AND current_display_user_id = ?
		) occupied_display_user_ids
		LIMIT 1
	`, appCode, displayUserID, appCode, displayUserID, appCode, displayUserID).Scan(&exists)
	if err == sql.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, err
	}

	return true, nil
}
