package strategy

import (
    "context"
    "crypto/sha256"
    "fmt"
    "testing"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "bindbox-game/internal/repository/mysql/dao"
    "bindbox-game/internal/repository/mysql/model"
)

func TestIchibanSelectItemBySlot_Deterministic(t *testing.T) {
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    if err != nil { t.Fatalf("db open err: %v", err) }
    if err := db.Exec(`CREATE TABLE activities (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        commitment_seed_master BLOB,
        commitment_state_version INTEGER
    );`).Error; err != nil { t.Fatalf("migrate err: %v", err) }
    if err := db.Exec(`CREATE TABLE activity_reward_settings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        created_at DATETIME,
        updated_at DATETIME,
        issue_id INTEGER NOT NULL,
        product_id INTEGER,
        name TEXT NOT NULL,
        weight INTEGER NOT NULL,
        quantity INTEGER NOT NULL,
        original_qty INTEGER NOT NULL,
        level INTEGER NOT NULL,
        sort INTEGER NOT NULL,
        is_boss INTEGER NOT NULL,
        deleted_at DATETIME
    );`).Error; err != nil { t.Fatalf("migrate err: %v", err) }
    q := dao.Use(db)

    // seed activity commitment
    _ = db.Exec(`INSERT INTO activities (id, commitment_seed_master, commitment_state_version) VALUES (9, x'7365656476616c7565', 1);`).Error

    // rewards with original qty
    r1 := &model.ActivityRewardSettings{IssueID: 1, Name: "A", Weight: 10, Quantity: 10, OriginalQty: 2, Level: 1, Sort: 1}
    r2 := &model.ActivityRewardSettings{IssueID: 1, Name: "B", Weight: 20, Quantity: 10, OriginalQty: 3, Level: 2, Sort: 2}
    if err := q.ActivityRewardSettings.Create(r1, r2); err != nil { t.Fatalf("create rewards err: %v", err) }

    s := NewIchiban(q, q)
    ctx := context.Background()
    total := int64(r1.OriginalQty + r2.OriginalQty)
    got1 := make([]int64, total)
    got2 := make([]int64, total)
    for i := int64(0); i < total; i++ {
        id, _, err := s.SelectItemBySlot(ctx, 9, 1, i)
        if err != nil { t.Fatalf("select err: %v", err) }
        got1[i] = id
    }
    for i := int64(0); i < total; i++ {
        id, _, err := s.SelectItemBySlot(ctx, 9, 1, i)
        if err != nil { t.Fatalf("select err: %v", err) }
        got2[i] = id
    }
    for i := int64(0); i < total; i++ {
        if got1[i] != got2[i] { t.Fatalf("non-deterministic mapping at %d: %d != %d", i, got1[i], got2[i]) }
    }
}

func TestIchibanSelectItemBySlot_OutOfRange(t *testing.T) {
    db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    _ = db.Exec(`CREATE TABLE activities (id INTEGER PRIMARY KEY AUTOINCREMENT, commitment_seed_master BLOB, commitment_state_version INTEGER);`).Error
    _ = db.Exec(`CREATE TABLE activity_reward_settings (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME, updated_at DATETIME, issue_id INTEGER NOT NULL, product_id INTEGER, name TEXT NOT NULL, weight INTEGER NOT NULL, quantity INTEGER NOT NULL, original_qty INTEGER NOT NULL, level INTEGER NOT NULL, sort INTEGER NOT NULL, is_boss INTEGER NOT NULL, deleted_at DATETIME);`).Error
    q := dao.Use(db)
    _ = db.Exec(`INSERT INTO activities (id, commitment_seed_master, commitment_state_version) VALUES (9, x'7365656476616c7565', 1);`).Error
    _ = q.ActivityRewardSettings.Create(&model.ActivityRewardSettings{IssueID: 2, Name: "A", OriginalQty: 1, Quantity: 1, Sort: 1})
    s := NewIchiban(q, q)
    if _, _, err := s.SelectItemBySlot(context.Background(), 9, 2, 5); err == nil { t.Fatalf("expected out of range error") }
}

func TestIchibanGrantReward_Decrement(t *testing.T) {
    db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    _ = db.Exec(`CREATE TABLE activities (id INTEGER PRIMARY KEY AUTOINCREMENT, commitment_seed_master BLOB, commitment_state_version INTEGER);`).Error
    _ = db.Exec(`CREATE TABLE activity_reward_settings (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME, updated_at DATETIME, issue_id INTEGER NOT NULL, product_id INTEGER, name TEXT NOT NULL, weight INTEGER NOT NULL, quantity INTEGER NOT NULL, original_qty INTEGER NOT NULL, level INTEGER NOT NULL, sort INTEGER NOT NULL, is_boss INTEGER NOT NULL, deleted_at DATETIME);`).Error
    q := dao.Use(db)
    r := &model.ActivityRewardSettings{IssueID: 3, Name: "A", OriginalQty: 1, Quantity: 2, Sort: 1}
    _ = q.ActivityRewardSettings.Create(r)
    s := NewIchiban(q, q)
    if err := s.GrantReward(context.Background(), 100, r.ID); err != nil { t.Fatalf("grant err: %v", err) }
    got, _ := q.ActivityRewardSettings.Where(q.ActivityRewardSettings.ID.Eq(r.ID)).First()
    if got.Quantity != 1 { t.Fatalf("quantity not decremented: %d", got.Quantity) }
}

func TestIchibanProofHasSeedHash(t *testing.T) {
	// 1. Setup In-Memory DB
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		t.Fatalf("db open err: %v", err)
	}

	// 2. Migrate Tables
	db.Exec(`CREATE TABLE activities (id INTEGER PRIMARY KEY AUTOINCREMENT, commitment_seed_master BLOB, commitment_state_version INTEGER, deleted_at DATETIME);`)
	db.Exec(`CREATE TABLE activity_reward_settings (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME, updated_at DATETIME, issue_id INTEGER NOT NULL, product_id INTEGER, name TEXT NOT NULL, weight INTEGER NOT NULL, quantity INTEGER NOT NULL, original_qty INTEGER NOT NULL, level INTEGER NOT NULL, sort INTEGER NOT NULL, is_boss INTEGER NOT NULL, deleted_at DATETIME);`)

	q := dao.Use(db)

	// 3. Insert Test Data
	seedBytes := []byte("testseedvalue")
	expectedHash := fmt.Sprintf("%x", sha256.Sum256(seedBytes))

	db.Exec("INSERT INTO activities (id, commitment_seed_master, commitment_state_version) VALUES (?, ?, ?)", 100, seedBytes, 1)

	r1 := &model.ActivityRewardSettings{IssueID: 10, Name: "A", Weight: 10, Quantity: 10, OriginalQty: 5, Level: 1, Sort: 1}
	q.ActivityRewardSettings.Create(r1)

	// 4. Test SelectItemBySlot
	s := NewIchiban(q, q)
	ctx := context.Background()

	_, proof, err := s.SelectItemBySlot(ctx, 100, 10, 0)
	if err != nil {
		t.Fatalf("SelectItemBySlot failed: %v", err)
	}

	// 5. Verify seed_hash is in proof
	if proof == nil {
		t.Fatal("proof is nil")
	}

	val, ok := proof["seed_hash"]
	if !ok {
		t.Fatal("seed_hash missing from proof")
	}

	seedHash, ok := val.(string)
	if !ok {
		t.Fatalf("seed_hash is not a string, got %T", val)
	}

	if seedHash != expectedHash {
		t.Fatalf("seed_hash mismatch. got %s, want %s", seedHash, expectedHash)
	}

	t.Logf("Success: seed_hash found in proof: %s", seedHash)
}
