package channelserver

import (
	"fmt"
	"testing"
	"time"

	"github.com/jmoiron/sqlx"
)

func setupGuildRepo(t *testing.T) (*GuildRepository, *sqlx.DB, uint32, uint32) {
	t.Helper()
	db := SetupTestDB(t)
	userID := CreateTestUser(t, db, "guild_test_user")
	charID := CreateTestCharacter(t, db, userID, "GuildLeader")
	repo := NewGuildRepository(db)
	guildID := CreateTestGuild(t, db, charID, "TestGuild")
	t.Cleanup(func() { TeardownTestDB(t, db) })
	return repo, db, guildID, charID
}

func TestGetByID(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	guild, err := repo.GetByID(guildID)
	if err != nil {
		t.Fatalf("GetByID failed: %v", err)
	}
	if guild == nil {
		t.Fatal("Expected guild, got nil")
	}
	if guild.ID != guildID {
		t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
	}
	if guild.Name != "TestGuild" {
		t.Errorf("Expected name 'TestGuild', got %q", guild.Name)
	}
	if guild.LeaderCharID != charID {
		t.Errorf("Expected leader %d, got %d", charID, guild.LeaderCharID)
	}
}

func TestGetByIDNotFound(t *testing.T) {
	repo, _, _, _ := setupGuildRepo(t)

	guild, err := repo.GetByID(999999)
	if err != nil {
		t.Fatalf("GetByID failed: %v", err)
	}
	if guild != nil {
		t.Errorf("Expected nil for non-existent guild, got: %+v", guild)
	}
}

func TestGetByCharID(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	guild, err := repo.GetByCharID(charID)
	if err != nil {
		t.Fatalf("GetByCharID failed: %v", err)
	}
	if guild == nil {
		t.Fatal("Expected guild, got nil")
	}
	if guild.ID != guildID {
		t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
	}
}

func TestGetByCharIDNotFound(t *testing.T) {
	repo, _, _, _ := setupGuildRepo(t)

	guild, err := repo.GetByCharID(999999)
	if err != nil {
		t.Fatalf("GetByCharID failed: %v", err)
	}
	if guild != nil {
		t.Errorf("Expected nil for non-member, got: %+v", guild)
	}
}

func TestCreate(t *testing.T) {
	db := SetupTestDB(t)
	defer TeardownTestDB(t, db)
	repo := NewGuildRepository(db)
	userID := CreateTestUser(t, db, "create_guild_user")
	charID := CreateTestCharacter(t, db, userID, "CreateLeader")

	guildID, err := repo.Create(charID, "NewGuild")
	if err != nil {
		t.Fatalf("Create failed: %v", err)
	}
	if guildID <= 0 {
		t.Errorf("Expected positive guild ID, got %d", guildID)
	}

	// Verify guild exists
	guild, err := repo.GetByID(uint32(guildID))
	if err != nil {
		t.Fatalf("GetByID after Create failed: %v", err)
	}
	if guild == nil {
		t.Fatal("Created guild not found")
	}
	if guild.Name != "NewGuild" {
		t.Errorf("Expected name 'NewGuild', got %q", guild.Name)
	}

	// Verify leader is a member
	member, err := repo.GetCharacterMembership(charID)
	if err != nil {
		t.Fatalf("GetCharacterMembership failed: %v", err)
	}
	if member == nil {
		t.Fatal("Leader not found as guild member")
	}
}

func TestSaveGuild(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	guild, err := repo.GetByID(guildID)
	if err != nil {
		t.Fatalf("GetByID failed: %v", err)
	}

	guild.Comment = "Updated comment"
	guild.MainMotto = 5
	guild.SubMotto = 3

	if err := repo.Save(guild); err != nil {
		t.Fatalf("Save failed: %v", err)
	}

	updated, err := repo.GetByID(guildID)
	if err != nil {
		t.Fatalf("GetByID after Save failed: %v", err)
	}
	if updated.Comment != "Updated comment" {
		t.Errorf("Expected comment 'Updated comment', got %q", updated.Comment)
	}
	if updated.MainMotto != 5 || updated.SubMotto != 3 {
		t.Errorf("Expected mottos 5/3, got %d/%d", updated.MainMotto, updated.SubMotto)
	}
}

func TestDisband(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.Disband(guildID); err != nil {
		t.Fatalf("Disband failed: %v", err)
	}

	guild, err := repo.GetByID(guildID)
	if err != nil {
		t.Fatalf("GetByID after Disband failed: %v", err)
	}
	if guild != nil {
		t.Errorf("Expected nil after disband, got: %+v", guild)
	}

	member, err := repo.GetCharacterMembership(charID)
	if err != nil {
		t.Fatalf("GetCharacterMembership after Disband failed: %v", err)
	}
	if member != nil {
		t.Errorf("Expected nil membership after disband, got: %+v", member)
	}
}

func TestGetMembers(t *testing.T) {
	repo, db, guildID, leaderID := setupGuildRepo(t)

	// Add a second member
	user2 := CreateTestUser(t, db, "member_user")
	member2 := CreateTestCharacter(t, db, user2, "Member2")
	if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, member2); err != nil {
		t.Fatalf("Failed to add member: %v", err)
	}

	members, err := repo.GetMembers(guildID, false)
	if err != nil {
		t.Fatalf("GetMembers failed: %v", err)
	}
	if len(members) != 2 {
		t.Fatalf("Expected 2 members, got %d", len(members))
	}

	ids := map[uint32]bool{leaderID: false, member2: false}
	for _, m := range members {
		ids[m.CharID] = true
	}
	if !ids[leaderID] || !ids[member2] {
		t.Errorf("Expected members %d and %d, got: %v", leaderID, member2, members)
	}
}

func TestGetCharacterMembership(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	member, err := repo.GetCharacterMembership(charID)
	if err != nil {
		t.Fatalf("GetCharacterMembership failed: %v", err)
	}
	if member == nil {
		t.Fatal("Expected membership, got nil")
	}
	if member.GuildID != guildID {
		t.Errorf("Expected guild ID %d, got %d", guildID, member.GuildID)
	}
	if !member.IsLeader {
		t.Error("Expected leader flag to be true")
	}
}

func TestSaveMember(t *testing.T) {
	repo, _, _, charID := setupGuildRepo(t)

	member, err := repo.GetCharacterMembership(charID)
	if err != nil {
		t.Fatalf("GetCharacterMembership failed: %v", err)
	}

	member.AvoidLeadership = true
	member.OrderIndex = 5

	if err := repo.SaveMember(member); err != nil {
		t.Fatalf("SaveMember failed: %v", err)
	}

	updated, err := repo.GetCharacterMembership(charID)
	if err != nil {
		t.Fatalf("GetCharacterMembership after Save failed: %v", err)
	}
	if !updated.AvoidLeadership {
		t.Error("Expected avoid_leadership=true")
	}
	if updated.OrderIndex != 5 {
		t.Errorf("Expected order_index=5, got %d", updated.OrderIndex)
	}
}

func TestRemoveCharacter(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	// Add and remove a member
	user2 := CreateTestUser(t, db, "remove_user")
	char2 := CreateTestCharacter(t, db, user2, "RemoveMe")
	if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
		t.Fatalf("Failed to add member: %v", err)
	}

	if err := repo.RemoveCharacter(char2); err != nil {
		t.Fatalf("RemoveCharacter failed: %v", err)
	}

	member, err := repo.GetCharacterMembership(char2)
	if err != nil {
		t.Fatalf("GetCharacterMembership after remove failed: %v", err)
	}
	if member != nil {
		t.Errorf("Expected nil membership after remove, got: %+v", member)
	}
}

func TestApplicationWorkflow(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "applicant_user")
	applicantID := CreateTestCharacter(t, db, user2, "Applicant")

	// Create application
	err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied)
	if err != nil {
		t.Fatalf("CreateApplication failed: %v", err)
	}

	// Check HasApplication
	has, err := repo.HasApplication(guildID, applicantID)
	if err != nil {
		t.Fatalf("HasApplication failed: %v", err)
	}
	if !has {
		t.Error("Expected application to exist")
	}

	// Get application
	app, err := repo.GetApplication(guildID, applicantID, GuildApplicationTypeApplied)
	if err != nil {
		t.Fatalf("GetApplication failed: %v", err)
	}
	if app == nil {
		t.Fatal("Expected application, got nil")
	}

	// Accept
	err = repo.AcceptApplication(guildID, applicantID)
	if err != nil {
		t.Fatalf("AcceptApplication failed: %v", err)
	}

	// Verify membership
	member, err := repo.GetCharacterMembership(applicantID)
	if err != nil {
		t.Fatalf("GetCharacterMembership after accept failed: %v", err)
	}
	if member == nil {
		t.Fatal("Expected membership after accept")
	}

	// Verify application removed
	has, err = repo.HasApplication(guildID, applicantID)
	if err != nil {
		t.Fatalf("HasApplication after accept failed: %v", err)
	}
	if has {
		t.Error("Expected no application after accept")
	}
}

func TestRejectApplication(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "reject_user")
	applicantID := CreateTestCharacter(t, db, user2, "Rejected")

	err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied)
	if err != nil {
		t.Fatalf("CreateApplication failed: %v", err)
	}

	err = repo.RejectApplication(guildID, applicantID)
	if err != nil {
		t.Fatalf("RejectApplication failed: %v", err)
	}

	has, err := repo.HasApplication(guildID, applicantID)
	if err != nil {
		t.Fatalf("HasApplication after reject failed: %v", err)
	}
	if has {
		t.Error("Expected no application after reject")
	}
}

func TestSetRecruiting(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.SetRecruiting(guildID, false); err != nil {
		t.Fatalf("SetRecruiting failed: %v", err)
	}

	var recruiting bool
	if err := db.QueryRow("SELECT recruiting FROM guilds WHERE id=$1", guildID).Scan(&recruiting); err != nil {
		t.Fatalf("Verification query failed: %v", err)
	}
	if recruiting {
		t.Error("Expected recruiting=false")
	}
}

func TestRPOperations(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	// AddRankRP
	if err := repo.AddRankRP(guildID, 100); err != nil {
		t.Fatalf("AddRankRP failed: %v", err)
	}
	var rankRP uint16
	if err := db.QueryRow("SELECT rank_rp FROM guilds WHERE id=$1", guildID).Scan(&rankRP); err != nil {
		t.Fatalf("Verification failed: %v", err)
	}
	if rankRP != 100 {
		t.Errorf("Expected rank_rp=100, got %d", rankRP)
	}

	// AddEventRP
	if err := repo.AddEventRP(guildID, 50); err != nil {
		t.Fatalf("AddEventRP failed: %v", err)
	}

	// ExchangeEventRP
	balance, err := repo.ExchangeEventRP(guildID, 20)
	if err != nil {
		t.Fatalf("ExchangeEventRP failed: %v", err)
	}
	if balance != 30 {
		t.Errorf("Expected event_rp balance=30, got %d", balance)
	}

	// Room RP operations
	if err := repo.AddRoomRP(guildID, 10); err != nil {
		t.Fatalf("AddRoomRP failed: %v", err)
	}
	roomRP, err := repo.GetRoomRP(guildID)
	if err != nil {
		t.Fatalf("GetRoomRP failed: %v", err)
	}
	if roomRP != 10 {
		t.Errorf("Expected room_rp=10, got %d", roomRP)
	}

	if err := repo.SetRoomRP(guildID, 0); err != nil {
		t.Fatalf("SetRoomRP failed: %v", err)
	}
	roomRP, err = repo.GetRoomRP(guildID)
	if err != nil {
		t.Fatalf("GetRoomRP after reset failed: %v", err)
	}
	if roomRP != 0 {
		t.Errorf("Expected room_rp=0, got %d", roomRP)
	}

	// SetRoomExpiry
	expiry := time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC)
	if err := repo.SetRoomExpiry(guildID, expiry); err != nil {
		t.Fatalf("SetRoomExpiry failed: %v", err)
	}
	var gotExpiry time.Time
	if err := db.QueryRow("SELECT room_expiry FROM guilds WHERE id=$1", guildID).Scan(&gotExpiry); err != nil {
		t.Fatalf("Verification failed: %v", err)
	}
	if !gotExpiry.Equal(expiry) {
		t.Errorf("Expected expiry %v, got %v", expiry, gotExpiry)
	}
}

func TestItemBox(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	// Initially nil
	data, err := repo.GetItemBox(guildID)
	if err != nil {
		t.Fatalf("GetItemBox failed: %v", err)
	}
	if data != nil {
		t.Errorf("Expected nil item box initially, got %x", data)
	}

	// Save and retrieve
	blob := []byte{0x01, 0x02, 0x03}
	if err := repo.SaveItemBox(guildID, blob); err != nil {
		t.Fatalf("SaveItemBox failed: %v", err)
	}

	data, err = repo.GetItemBox(guildID)
	if err != nil {
		t.Fatalf("GetItemBox after save failed: %v", err)
	}
	if len(data) != 3 || data[0] != 0x01 || data[2] != 0x03 {
		t.Errorf("Expected %x, got %x", blob, data)
	}
}

func TestListAll(t *testing.T) {
	repo, db, _, _ := setupGuildRepo(t)

	// Create a second guild
	user2 := CreateTestUser(t, db, "list_user")
	char2 := CreateTestCharacter(t, db, user2, "ListLeader")
	CreateTestGuild(t, db, char2, "SecondGuild")

	guilds, err := repo.ListAll()
	if err != nil {
		t.Fatalf("ListAll failed: %v", err)
	}
	if len(guilds) < 2 {
		t.Errorf("Expected at least 2 guilds, got %d", len(guilds))
	}
}

func TestArrangeCharacters(t *testing.T) {
	repo, db, guildID, leaderID := setupGuildRepo(t)

	// Add two more members
	user2 := CreateTestUser(t, db, "arrange_user2")
	char2 := CreateTestCharacter(t, db, user2, "Char2")
	user3 := CreateTestUser(t, db, "arrange_user3")
	char3 := CreateTestCharacter(t, db, user3, "Char3")
	if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
		t.Fatalf("Failed to add member: %v", err)
	}
	if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 3)", guildID, char3); err != nil {
		t.Fatalf("Failed to add member: %v", err)
	}

	// Rearrange (excludes leader, sets order_index starting at 2)
	if err := repo.ArrangeCharacters([]uint32{char3, char2}); err != nil {
		t.Fatalf("ArrangeCharacters failed: %v", err)
	}

	// Verify order changed
	var order2, order3 uint16
	_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char2).Scan(&order2)
	_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char3).Scan(&order3)
	if order3 != 2 || order2 != 3 {
		t.Errorf("Expected char3=2, char2=3 but got char3=%d, char2=%d", order3, order2)
	}
	_ = leaderID
}

func TestSetRecruiter(t *testing.T) {
	repo, db, _, charID := setupGuildRepo(t)

	if err := repo.SetRecruiter(charID, true); err != nil {
		t.Fatalf("SetRecruiter failed: %v", err)
	}

	var recruiter bool
	if err := db.QueryRow("SELECT recruiter FROM guild_characters WHERE character_id=$1", charID).Scan(&recruiter); err != nil {
		t.Fatalf("Verification failed: %v", err)
	}
	if !recruiter {
		t.Error("Expected recruiter=true")
	}
}

func TestAddMemberDailyRP(t *testing.T) {
	repo, db, _, charID := setupGuildRepo(t)

	if err := repo.AddMemberDailyRP(charID, 25); err != nil {
		t.Fatalf("AddMemberDailyRP failed: %v", err)
	}

	var rp uint16
	if err := db.QueryRow("SELECT rp_today FROM guild_characters WHERE character_id=$1", charID).Scan(&rp); err != nil {
		t.Fatalf("Verification failed: %v", err)
	}
	if rp != 25 {
		t.Errorf("Expected rp_today=25, got %d", rp)
	}
}

// --- Invitation / Scout tests ---

func TestCancelInvite(t *testing.T) {
	repo, db, guildID, leaderID := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "invite_user")
	char2 := CreateTestCharacter(t, db, user2, "Invited")

	if err := repo.CreateInviteWithMail(guildID, char2, leaderID, leaderID, char2, "Invite", "body"); err != nil {
		t.Fatalf("CreateInviteWithMail failed: %v", err)
	}

	invites, err := repo.ListInvites(guildID)
	if err != nil || len(invites) != 1 {
		t.Fatalf("Expected 1 invite, got %d (err: %v)", len(invites), err)
	}

	if err := repo.CancelInvite(invites[0].ID); err != nil {
		t.Fatalf("CancelInvite failed: %v", err)
	}

	has, err := repo.HasInvite(guildID, char2)
	if err != nil {
		t.Fatalf("HasInvite failed: %v", err)
	}
	if has {
		t.Error("Expected no invite after cancellation")
	}
}

func TestListInvites(t *testing.T) {
	repo, db, guildID, leaderID := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "scout_user")
	char2 := CreateTestCharacter(t, db, user2, "Scouted")

	if err := repo.CreateInviteWithMail(guildID, char2, leaderID, leaderID, char2, "Invite", "body"); err != nil {
		t.Fatalf("CreateInviteWithMail failed: %v", err)
	}

	invites, err := repo.ListInvites(guildID)
	if err != nil {
		t.Fatalf("ListInvites failed: %v", err)
	}
	if len(invites) != 1 {
		t.Fatalf("Expected 1 invite, got %d", len(invites))
	}
	if invites[0].CharID != char2 {
		t.Errorf("Expected char ID %d, got %d", char2, invites[0].CharID)
	}
	if invites[0].Name != "Scouted" {
		t.Errorf("Expected name 'Scouted', got %q", invites[0].Name)
	}
	if invites[0].ActorID != leaderID {
		t.Errorf("Expected actor ID %d, got %d", leaderID, invites[0].ActorID)
	}
	if invites[0].ID == 0 {
		t.Error("Expected non-zero invite ID")
	}
	if invites[0].InvitedAt.IsZero() {
		t.Error("Expected non-zero InvitedAt timestamp")
	}
}

func TestListInvitesEmpty(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	invites, err := repo.ListInvites(guildID)
	if err != nil {
		t.Fatalf("ListInvites failed: %v", err)
	}
	if len(invites) != 0 {
		t.Errorf("Expected 0 invites, got %d", len(invites))
	}
}

func TestGetByCharIDWithApplication(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "app_char_user")
	char2 := CreateTestCharacter(t, db, user2, "Applicant2")

	if err := repo.CreateApplication(guildID, char2, char2, GuildApplicationTypeApplied); err != nil {
		t.Fatalf("CreateApplication failed: %v", err)
	}

	guild, err := repo.GetByCharID(char2)
	if err != nil {
		t.Fatalf("GetByCharID failed: %v", err)
	}
	if guild == nil {
		t.Fatal("Expected guild via application, got nil")
	}
	if guild.ID != guildID {
		t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
	}
}

func TestGetMembersApplicants(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "applicant_member_user")
	char2 := CreateTestCharacter(t, db, user2, "AppMember")

	if err := repo.CreateApplication(guildID, char2, char2, GuildApplicationTypeApplied); err != nil {
		t.Fatalf("CreateApplication failed: %v", err)
	}

	applicants, err := repo.GetMembers(guildID, true)
	if err != nil {
		t.Fatalf("GetMembers(applicants=true) failed: %v", err)
	}
	if len(applicants) != 1 {
		t.Fatalf("Expected 1 applicant, got %d", len(applicants))
	}
	if applicants[0].CharID != char2 {
		t.Errorf("Expected applicant char ID %d, got %d", char2, applicants[0].CharID)
	}
	if !applicants[0].IsApplicant {
		t.Error("Expected IsApplicant=true")
	}
}

// --- SetPugiOutfits ---

func TestSetPugiOutfits(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.SetPugiOutfits(guildID, 0xFF); err != nil {
		t.Fatalf("SetPugiOutfits failed: %v", err)
	}

	var outfits uint32
	if err := db.QueryRow("SELECT pugi_outfits FROM guilds WHERE id=$1", guildID).Scan(&outfits); err != nil {
		t.Fatalf("Verification failed: %v", err)
	}
	if outfits != 0xFF {
		t.Errorf("Expected pugi_outfits=0xFF, got %d", outfits)
	}
}

// --- Guild Posts ---

func TestCreateAndListPosts(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)
	_ = db

	if err := repo.CreatePost(guildID, charID, 1, 0, "Hello", "World", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}
	if err := repo.CreatePost(guildID, charID, 2, 0, "Second", "Post", 10); err != nil {
		t.Fatalf("CreatePost 2 failed: %v", err)
	}

	posts, err := repo.ListPosts(guildID, 0)
	if err != nil {
		t.Fatalf("ListPosts failed: %v", err)
	}
	if len(posts) != 2 {
		t.Fatalf("Expected 2 posts, got %d", len(posts))
	}
	// Newest first
	if posts[0].Title != "Second" {
		t.Errorf("Expected newest first, got %q", posts[0].Title)
	}
}

func TestCreatePostMaxPosts(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	// Create 3 posts with maxPosts=2 — the oldest should be soft-deleted
	for i := 0; i < 3; i++ {
		if err := repo.CreatePost(guildID, charID, 0, 0, fmt.Sprintf("Post%d", i), "body", 2); err != nil {
			t.Fatalf("CreatePost %d failed: %v", i, err)
		}
	}

	posts, err := repo.ListPosts(guildID, 0)
	if err != nil {
		t.Fatalf("ListPosts failed: %v", err)
	}
	if len(posts) != 2 {
		t.Errorf("Expected 2 posts after max enforcement, got %d", len(posts))
	}
}

func TestDeletePost(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreatePost(guildID, charID, 0, 0, "ToDelete", "body", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}
	posts, _ := repo.ListPosts(guildID, 0)
	if len(posts) == 0 {
		t.Fatal("Expected post to exist")
	}

	if err := repo.DeletePost(posts[0].ID); err != nil {
		t.Fatalf("DeletePost failed: %v", err)
	}

	posts, _ = repo.ListPosts(guildID, 0)
	if len(posts) != 0 {
		t.Errorf("Expected 0 posts after delete, got %d", len(posts))
	}
}

func TestUpdatePost(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreatePost(guildID, charID, 0, 0, "Original", "body", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}
	posts, _ := repo.ListPosts(guildID, 0)

	if err := repo.UpdatePost(posts[0].ID, "Updated", "new body"); err != nil {
		t.Fatalf("UpdatePost failed: %v", err)
	}

	posts, _ = repo.ListPosts(guildID, 0)
	if posts[0].Title != "Updated" || posts[0].Body != "new body" {
		t.Errorf("Expected 'Updated'/'new body', got %q/%q", posts[0].Title, posts[0].Body)
	}
}

func TestUpdatePostStamp(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreatePost(guildID, charID, 0, 0, "Stamp", "body", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}
	posts, _ := repo.ListPosts(guildID, 0)

	if err := repo.UpdatePostStamp(posts[0].ID, 42); err != nil {
		t.Fatalf("UpdatePostStamp failed: %v", err)
	}

	posts, _ = repo.ListPosts(guildID, 0)
	if posts[0].StampID != 42 {
		t.Errorf("Expected stamp_id=42, got %d", posts[0].StampID)
	}
}

func TestPostLikedBy(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreatePost(guildID, charID, 0, 0, "Like", "body", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}
	posts, _ := repo.ListPosts(guildID, 0)

	if err := repo.SetPostLikedBy(posts[0].ID, "100,200"); err != nil {
		t.Fatalf("SetPostLikedBy failed: %v", err)
	}

	liked, err := repo.GetPostLikedBy(posts[0].ID)
	if err != nil {
		t.Fatalf("GetPostLikedBy failed: %v", err)
	}
	if liked != "100,200" {
		t.Errorf("Expected '100,200', got %q", liked)
	}
}

func TestCountNewPosts(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	since := time.Now().Add(-1 * time.Hour)

	if err := repo.CreatePost(guildID, charID, 0, 0, "New", "body", 10); err != nil {
		t.Fatalf("CreatePost failed: %v", err)
	}

	count, err := repo.CountNewPosts(guildID, since)
	if err != nil {
		t.Fatalf("CountNewPosts failed: %v", err)
	}
	if count != 1 {
		t.Errorf("Expected 1 new post, got %d", count)
	}

	// Future time should yield 0
	count, err = repo.CountNewPosts(guildID, time.Now().Add(1*time.Hour))
	if err != nil {
		t.Fatalf("CountNewPosts (future) failed: %v", err)
	}
	if count != 0 {
		t.Errorf("Expected 0 new posts with future time, got %d", count)
	}
}

func TestListPostsByType(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreatePost(guildID, charID, 0, 0, "TypeA", "body", 10); err != nil {
		t.Fatalf("CreatePost type 0 failed: %v", err)
	}
	if err := repo.CreatePost(guildID, charID, 0, 1, "TypeB", "body", 10); err != nil {
		t.Fatalf("CreatePost type 1 failed: %v", err)
	}

	posts0, _ := repo.ListPosts(guildID, 0)
	posts1, _ := repo.ListPosts(guildID, 1)
	if len(posts0) != 1 {
		t.Errorf("Expected 1 type-0 post, got %d", len(posts0))
	}
	if len(posts1) != 1 {
		t.Errorf("Expected 1 type-1 post, got %d", len(posts1))
	}
}

// --- Guild Alliances ---

func TestCreateAndGetAlliance(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAlliance("TestAlliance", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}

	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Alliance not found in DB: %v", err)
	}

	alliance, err := repo.GetAllianceByID(allianceID)
	if err != nil {
		t.Fatalf("GetAllianceByID failed: %v", err)
	}
	if alliance == nil {
		t.Fatal("Expected alliance, got nil")
	}
	if alliance.Name != "TestAlliance" {
		t.Errorf("Expected name 'TestAlliance', got %q", alliance.Name)
	}
	if alliance.ParentGuildID != guildID {
		t.Errorf("Expected parent guild %d, got %d", guildID, alliance.ParentGuildID)
	}
	if alliance.ParentGuild.ID != guildID {
		t.Errorf("Expected populated ParentGuild.ID=%d, got %d", guildID, alliance.ParentGuild.ID)
	}
}

func TestGetAllianceByIDNotFound(t *testing.T) {
	repo, _, _, _ := setupGuildRepo(t)

	alliance, err := repo.GetAllianceByID(999999)
	if err != nil {
		t.Fatalf("GetAllianceByID failed: %v", err)
	}
	if alliance != nil {
		t.Errorf("Expected nil for non-existent alliance, got: %+v", alliance)
	}
}

func TestListAlliances(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAlliance("Alliance1", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}

	// Create a second guild and alliance
	user2 := CreateTestUser(t, db, "alliance_user2")
	char2 := CreateTestCharacter(t, db, user2, "AlliLeader2")
	guild2 := CreateTestGuild(t, db, char2, "AlliGuild2")
	if err := repo.CreateAlliance("Alliance2", guild2); err != nil {
		t.Fatalf("CreateAlliance 2 failed: %v", err)
	}

	alliances, err := repo.ListAlliances()
	if err != nil {
		t.Fatalf("ListAlliances failed: %v", err)
	}
	if len(alliances) < 2 {
		t.Errorf("Expected at least 2 alliances, got %d", len(alliances))
	}
}

func TestDeleteAlliance(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAlliance("ToDelete", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}

	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Alliance not found: %v", err)
	}

	if err := repo.DeleteAlliance(allianceID); err != nil {
		t.Fatalf("DeleteAlliance failed: %v", err)
	}

	alliance, err := repo.GetAllianceByID(allianceID)
	if err != nil {
		t.Fatalf("GetAllianceByID after delete failed: %v", err)
	}
	if alliance != nil {
		t.Errorf("Expected nil after delete, got: %+v", alliance)
	}
}

func TestRemoveGuildFromAllianceSub1(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "alli_sub1_user")
	char2 := CreateTestCharacter(t, db, user2, "Sub1Leader")
	guild2 := CreateTestGuild(t, db, char2, "SubGuild1")

	if err := repo.CreateAlliance("AlliSub", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}
	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Failed to get alliance ID: %v", err)
	}

	// Add sub1
	if _, err := db.Exec("UPDATE guild_alliances SET sub1_id=$1 WHERE id=$2", guild2, allianceID); err != nil {
		t.Fatalf("Failed to set sub1: %v", err)
	}

	// Remove sub1
	if err := repo.RemoveGuildFromAlliance(allianceID, guild2, guild2, 0); err != nil {
		t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
	}

	alliance, err := repo.GetAllianceByID(allianceID)
	if err != nil {
		t.Fatalf("GetAllianceByID failed: %v", err)
	}
	if alliance == nil {
		t.Fatal("Expected alliance to still exist")
	}
	if alliance.SubGuild1ID != 0 {
		t.Errorf("Expected sub1_id=0, got %d", alliance.SubGuild1ID)
	}
}

func TestRemoveGuildFromAllianceSub1ShiftsSub2(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "alli_shift_user2")
	char2 := CreateTestCharacter(t, db, user2, "Shift2Leader")
	guild2 := CreateTestGuild(t, db, char2, "ShiftGuild2")

	user3 := CreateTestUser(t, db, "alli_shift_user3")
	char3 := CreateTestCharacter(t, db, user3, "Shift3Leader")
	guild3 := CreateTestGuild(t, db, char3, "ShiftGuild3")

	if err := repo.CreateAlliance("AlliShift", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}
	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Failed to get alliance ID: %v", err)
	}
	if _, err := db.Exec("UPDATE guild_alliances SET sub1_id=$1, sub2_id=$2 WHERE id=$3", guild2, guild3, allianceID); err != nil {
		t.Fatalf("Failed to set sub guilds: %v", err)
	}

	// Remove sub1 — sub2 should shift into sub1's slot
	if err := repo.RemoveGuildFromAlliance(allianceID, guild2, guild2, guild3); err != nil {
		t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
	}

	alliance, err := repo.GetAllianceByID(allianceID)
	if err != nil {
		t.Fatalf("GetAllianceByID failed: %v", err)
	}
	if alliance == nil {
		t.Fatal("Expected alliance to still exist")
	}
	if alliance.SubGuild1ID != guild3 {
		t.Errorf("Expected sub1_id=%d (shifted from sub2), got %d", guild3, alliance.SubGuild1ID)
	}
	if alliance.SubGuild2ID != 0 {
		t.Errorf("Expected sub2_id=0, got %d", alliance.SubGuild2ID)
	}
}

func TestRemoveGuildFromAllianceSub2(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "alli_s2_user2")
	char2 := CreateTestCharacter(t, db, user2, "S2Leader2")
	guild2 := CreateTestGuild(t, db, char2, "S2Guild2")

	user3 := CreateTestUser(t, db, "alli_s2_user3")
	char3 := CreateTestCharacter(t, db, user3, "S2Leader3")
	guild3 := CreateTestGuild(t, db, char3, "S2Guild3")

	if err := repo.CreateAlliance("AlliS2", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}
	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Failed to get alliance ID: %v", err)
	}
	if _, err := db.Exec("UPDATE guild_alliances SET sub1_id=$1, sub2_id=$2 WHERE id=$3", guild2, guild3, allianceID); err != nil {
		t.Fatalf("Failed to set sub guilds: %v", err)
	}

	// Remove sub2 directly
	if err := repo.RemoveGuildFromAlliance(allianceID, guild3, guild2, guild3); err != nil {
		t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
	}

	alliance, err := repo.GetAllianceByID(allianceID)
	if err != nil {
		t.Fatalf("GetAllianceByID failed: %v", err)
	}
	if alliance == nil {
		t.Fatal("Expected alliance to still exist")
	}
	if alliance.SubGuild1ID != guild2 {
		t.Errorf("Expected sub1_id=%d unchanged, got %d", guild2, alliance.SubGuild1ID)
	}
	if alliance.SubGuild2ID != 0 {
		t.Errorf("Expected sub2_id=0, got %d", alliance.SubGuild2ID)
	}
}

// --- Guild Adventures ---

func TestCreateAndListAdventures(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAdventure(guildID, 5, 1000, 2000); err != nil {
		t.Fatalf("CreateAdventure failed: %v", err)
	}

	adventures, err := repo.ListAdventures(guildID)
	if err != nil {
		t.Fatalf("ListAdventures failed: %v", err)
	}
	if len(adventures) != 1 {
		t.Fatalf("Expected 1 adventure, got %d", len(adventures))
	}
	if adventures[0].Destination != 5 {
		t.Errorf("Expected destination=5, got %d", adventures[0].Destination)
	}
	if adventures[0].Depart != 1000 {
		t.Errorf("Expected depart=1000, got %d", adventures[0].Depart)
	}
	if adventures[0].Return != 2000 {
		t.Errorf("Expected return=2000, got %d", adventures[0].Return)
	}
}

func TestCreateAdventureWithCharge(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAdventureWithCharge(guildID, 3, 50, 1000, 2000); err != nil {
		t.Fatalf("CreateAdventureWithCharge failed: %v", err)
	}

	adventures, err := repo.ListAdventures(guildID)
	if err != nil {
		t.Fatalf("ListAdventures failed: %v", err)
	}
	if len(adventures) != 1 {
		t.Fatalf("Expected 1 adventure, got %d", len(adventures))
	}
	if adventures[0].Charge != 50 {
		t.Errorf("Expected charge=50, got %d", adventures[0].Charge)
	}
}

func TestChargeAdventure(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	if err := repo.CreateAdventure(guildID, 1, 1000, 2000); err != nil {
		t.Fatalf("CreateAdventure failed: %v", err)
	}
	adventures, _ := repo.ListAdventures(guildID)
	advID := adventures[0].ID

	if err := repo.ChargeAdventure(advID, 25); err != nil {
		t.Fatalf("ChargeAdventure failed: %v", err)
	}

	var charge uint32
	if err := db.QueryRow("SELECT charge FROM guild_adventures WHERE id=$1", advID).Scan(&charge); err != nil {
		t.Fatalf("Failed to get charge: %v", err)
	}
	if charge != 25 {
		t.Errorf("Expected charge=25, got %d", charge)
	}
}

func TestCollectAdventure(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	if err := repo.CreateAdventure(guildID, 1, 1000, 2000); err != nil {
		t.Fatalf("CreateAdventure failed: %v", err)
	}
	adventures, _ := repo.ListAdventures(guildID)
	advID := adventures[0].ID

	if err := repo.CollectAdventure(advID, charID); err != nil {
		t.Fatalf("CollectAdventure failed: %v", err)
	}

	// Verify collected_by updated
	adventures, _ = repo.ListAdventures(guildID)
	if adventures[0].CollectedBy == "" {
		t.Error("Expected collected_by to be non-empty")
	}
}

func TestListAdventuresEmpty(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	adventures, err := repo.ListAdventures(guildID)
	if err != nil {
		t.Fatalf("ListAdventures failed: %v", err)
	}
	if len(adventures) != 0 {
		t.Errorf("Expected 0 adventures, got %d", len(adventures))
	}
}

// --- Guild Treasure Hunts ---

func TestCreateAndGetPendingHunt(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	huntData := []byte{0xAA, 0xBB, 0xCC}
	if err := repo.CreateHunt(guildID, charID, 10, 1, huntData, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}

	hunt, err := repo.GetPendingHunt(charID)
	if err != nil {
		t.Fatalf("GetPendingHunt failed: %v", err)
	}
	if hunt == nil {
		t.Fatal("Expected pending hunt, got nil")
	}
	if hunt.HostID != charID {
		t.Errorf("Expected host_id=%d, got %d", charID, hunt.HostID)
	}
	if hunt.Destination != 10 {
		t.Errorf("Expected destination=10, got %d", hunt.Destination)
	}
	if hunt.Level != 1 {
		t.Errorf("Expected level=1, got %d", hunt.Level)
	}
	if len(hunt.HuntData) != 3 || hunt.HuntData[0] != 0xAA {
		t.Errorf("Expected hunt_data [AA BB CC], got %x", hunt.HuntData)
	}
}

func TestGetPendingHuntNone(t *testing.T) {
	repo, _, _, charID := setupGuildRepo(t)

	hunt, err := repo.GetPendingHunt(charID)
	if err != nil {
		t.Fatalf("GetPendingHunt failed: %v", err)
	}
	if hunt != nil {
		t.Errorf("Expected nil when no pending hunt, got: %+v", hunt)
	}
}

func TestAcquireHunt(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)

	if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}
	hunt, _ := repo.GetPendingHunt(charID)

	if err := repo.AcquireHunt(hunt.HuntID); err != nil {
		t.Fatalf("AcquireHunt failed: %v", err)
	}

	// After acquiring, it should no longer appear as pending
	pending, _ := repo.GetPendingHunt(charID)
	if pending != nil {
		t.Error("Expected no pending hunt after acquire")
	}

	// Verify in DB
	var acquired bool
	if err := db.QueryRow("SELECT acquired FROM guild_hunts WHERE id=$1", hunt.HuntID).Scan(&acquired); err != nil {
		t.Fatalf("Failed to get acquired: %v", err)
	}
	if !acquired {
		t.Error("Expected acquired=true in DB")
	}
}

func TestListGuildHunts(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	// Create a level-2 hunt and acquire it
	if err := repo.CreateHunt(guildID, charID, 10, 2, []byte{0x01}, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}
	hunt, _ := repo.GetPendingHunt(charID)
	if err := repo.AcquireHunt(hunt.HuntID); err != nil {
		t.Fatalf("AcquireHunt failed: %v", err)
	}

	// Create a level-1 hunt (should not appear)
	if err := repo.CreateHunt(guildID, charID, 20, 1, nil, ""); err != nil {
		t.Fatalf("CreateHunt level-1 failed: %v", err)
	}

	hunts, err := repo.ListGuildHunts(guildID, charID)
	if err != nil {
		t.Fatalf("ListGuildHunts failed: %v", err)
	}
	if len(hunts) != 1 {
		t.Fatalf("Expected 1 acquired level-2 hunt, got %d", len(hunts))
	}
	if hunts[0].Destination != 10 {
		t.Errorf("Expected destination=10, got %d", hunts[0].Destination)
	}
}

func TestRegisterHuntReport(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)

	if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}
	hunt, _ := repo.GetPendingHunt(charID)

	if err := repo.RegisterHuntReport(hunt.HuntID, charID); err != nil {
		t.Fatalf("RegisterHuntReport failed: %v", err)
	}

	var treasureHunt *uint32
	if err := db.QueryRow("SELECT treasure_hunt FROM guild_characters WHERE character_id=$1", charID).Scan(&treasureHunt); err != nil {
		t.Fatalf("Failed to get treasure_hunt: %v", err)
	}
	if treasureHunt == nil || *treasureHunt != hunt.HuntID {
		t.Errorf("Expected treasure_hunt=%d, got %v", hunt.HuntID, treasureHunt)
	}
}

func TestCollectHunt(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)

	if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}
	hunt, _ := repo.GetPendingHunt(charID)
	if err := repo.RegisterHuntReport(hunt.HuntID, charID); err != nil {
		t.Fatalf("RegisterHuntReport failed: %v", err)
	}

	if err := repo.CollectHunt(hunt.HuntID); err != nil {
		t.Fatalf("CollectHunt failed: %v", err)
	}

	// Hunt should be marked collected
	var collected bool
	if err := db.QueryRow("SELECT collected FROM guild_hunts WHERE id=$1", hunt.HuntID).Scan(&collected); err != nil {
		t.Fatalf("Failed to scan collected: %v", err)
	}
	if !collected {
		t.Error("Expected collected=true")
	}

	// Character's treasure_hunt should be cleared
	var treasureHunt *uint32
	if err := db.QueryRow("SELECT treasure_hunt FROM guild_characters WHERE character_id=$1", charID).Scan(&treasureHunt); err != nil {
		t.Fatalf("Failed to get treasure_hunt: %v", err)
	}
	if treasureHunt != nil {
		t.Errorf("Expected treasure_hunt=NULL, got %v", *treasureHunt)
	}
}

func TestClaimHuntReward(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)

	if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
		t.Fatalf("CreateHunt failed: %v", err)
	}
	hunt, _ := repo.GetPendingHunt(charID)

	if err := repo.ClaimHuntReward(hunt.HuntID, charID); err != nil {
		t.Fatalf("ClaimHuntReward failed: %v", err)
	}

	var count int
	if err := db.QueryRow("SELECT COUNT(*) FROM guild_hunts_claimed WHERE hunt_id=$1 AND character_id=$2", hunt.HuntID, charID).Scan(&count); err != nil {
		t.Fatalf("Failed to scan claimed count: %v", err)
	}
	if count != 1 {
		t.Errorf("Expected 1 claimed entry, got %d", count)
	}
}

// --- Guild Meals ---

func TestCreateAndListMeals(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	now := time.Now().UTC().Truncate(time.Second)
	id, err := repo.CreateMeal(guildID, 5, 3, now)
	if err != nil {
		t.Fatalf("CreateMeal failed: %v", err)
	}
	if id == 0 {
		t.Error("Expected non-zero meal ID")
	}

	meals, err := repo.ListMeals(guildID)
	if err != nil {
		t.Fatalf("ListMeals failed: %v", err)
	}
	if len(meals) != 1 {
		t.Fatalf("Expected 1 meal, got %d", len(meals))
	}
	if meals[0].MealID != 5 {
		t.Errorf("Expected meal_id=5, got %d", meals[0].MealID)
	}
	if meals[0].Level != 3 {
		t.Errorf("Expected level=3, got %d", meals[0].Level)
	}
}

func TestUpdateMeal(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	now := time.Now().UTC().Truncate(time.Second)
	id, _ := repo.CreateMeal(guildID, 5, 3, now)

	later := now.Add(30 * time.Minute)
	if err := repo.UpdateMeal(id, 10, 5, later); err != nil {
		t.Fatalf("UpdateMeal failed: %v", err)
	}

	meals, _ := repo.ListMeals(guildID)
	if meals[0].MealID != 10 {
		t.Errorf("Expected meal_id=10, got %d", meals[0].MealID)
	}
	if meals[0].Level != 5 {
		t.Errorf("Expected level=5, got %d", meals[0].Level)
	}
}

func TestListMealsEmpty(t *testing.T) {
	repo, _, guildID, _ := setupGuildRepo(t)

	meals, err := repo.ListMeals(guildID)
	if err != nil {
		t.Fatalf("ListMeals failed: %v", err)
	}
	if len(meals) != 0 {
		t.Errorf("Expected 0 meals, got %d", len(meals))
	}
}

// --- Kill tracking ---

func TestClaimHuntBox(t *testing.T) {
	repo, db, _, charID := setupGuildRepo(t)

	claimedAt := time.Now().UTC().Truncate(time.Second)
	if err := repo.ClaimHuntBox(charID, claimedAt); err != nil {
		t.Fatalf("ClaimHuntBox failed: %v", err)
	}

	var got time.Time
	if err := db.QueryRow("SELECT box_claimed FROM guild_characters WHERE character_id=$1", charID).Scan(&got); err != nil {
		t.Fatalf("Failed to scan box_claimed: %v", err)
	}
	if !got.Equal(claimedAt) {
		t.Errorf("Expected box_claimed=%v, got %v", claimedAt, got)
	}
}

func TestListAndCountGuildKills(t *testing.T) {
	repo, db, guildID, charID := setupGuildRepo(t)

	// Set box_claimed to the past so kills after it are visible
	past := time.Now().Add(-1 * time.Hour).UTC().Truncate(time.Second)
	if err := repo.ClaimHuntBox(charID, past); err != nil {
		t.Fatalf("ClaimHuntBox failed: %v", err)
	}

	// Insert kill logs for this character
	if _, err := db.Exec("INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, 100, 1, NOW())", charID); err != nil {
		t.Fatalf("Failed to insert kill log: %v", err)
	}
	if _, err := db.Exec("INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, 200, 1, NOW())", charID); err != nil {
		t.Fatalf("Failed to insert kill log: %v", err)
	}

	kills, err := repo.ListGuildKills(guildID, charID)
	if err != nil {
		t.Fatalf("ListGuildKills failed: %v", err)
	}
	if len(kills) != 2 {
		t.Fatalf("Expected 2 kills, got %d", len(kills))
	}

	count, err := repo.CountGuildKills(guildID, charID)
	if err != nil {
		t.Fatalf("CountGuildKills failed: %v", err)
	}
	if count != 2 {
		t.Errorf("Expected count=2, got %d", count)
	}
}

func TestListGuildKillsEmpty(t *testing.T) {
	repo, _, guildID, charID := setupGuildRepo(t)

	// Set box_claimed to now — no kills after it
	if err := repo.ClaimHuntBox(charID, time.Now().UTC()); err != nil {
		t.Fatalf("ClaimHuntBox failed: %v", err)
	}

	kills, err := repo.ListGuildKills(guildID, charID)
	if err != nil {
		t.Fatalf("ListGuildKills failed: %v", err)
	}
	if len(kills) != 0 {
		t.Errorf("Expected 0 kills, got %d", len(kills))
	}

	count, err := repo.CountGuildKills(guildID, charID)
	if err != nil {
		t.Fatalf("CountGuildKills failed: %v", err)
	}
	if count != 0 {
		t.Errorf("Expected count=0, got %d", count)
	}
}

// --- Disband with alliance cleanup ---

func TestDisbandCleansUpAlliance(t *testing.T) {
	repo, db, guildID, _ := setupGuildRepo(t)

	// Create alliance with this guild as parent
	if err := repo.CreateAlliance("DisbandAlliance", guildID); err != nil {
		t.Fatalf("CreateAlliance failed: %v", err)
	}

	var allianceID uint32
	if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
		t.Fatalf("Failed to scan alliance ID: %v", err)
	}

	if err := repo.Disband(guildID); err != nil {
		t.Fatalf("Disband failed: %v", err)
	}

	// Alliance should be deleted too (parent_id match in Disband)
	alliance, _ := repo.GetAllianceByID(allianceID)
	if alliance != nil {
		t.Errorf("Expected alliance to be deleted after parent guild disband, got: %+v", alliance)
	}
}

// --- CreateInviteWithMail ---

func TestCreateInviteWithMail(t *testing.T) {
	repo, db, guildID, leaderID := setupGuildRepo(t)

	user2 := CreateTestUser(t, db, "scout_mail_user")
	char2 := CreateTestCharacter(t, db, user2, "ScoutTarget")

	err := repo.CreateInviteWithMail(guildID, char2, leaderID, leaderID, char2, "Guild Invite", "You have been invited!")
	if err != nil {
		t.Fatalf("CreateInviteWithMail failed: %v", err)
	}

	// Verify invite was created
	has, err := repo.HasInvite(guildID, char2)
	if err != nil {
		t.Fatalf("HasInvite failed: %v", err)
	}
	if !has {
		t.Error("Expected invite to exist after CreateInviteWithMail")
	}

	// Verify mail was sent
	var mailCount int
	if err := db.QueryRow(
		"SELECT COUNT(*) FROM mail WHERE sender_id=$1 AND recipient_id=$2 AND subject=$3",
		leaderID, char2, "Guild Invite").Scan(&mailCount); err != nil {
		t.Fatalf("Mail verification query failed: %v", err)
	}
	if mailCount != 1 {
		t.Errorf("Expected 1 mail row, got %d", mailCount)
	}
}
