package guilds

import (
	"context"
	"fmt"
	"path/filepath"
	"testing"
	"time"

	"zombiezen.com/go/sqlite"
	"zombiezen.com/go/sqlite/sqlitex"
)

// createTestPool creates a temporary test database pool
func createTestPool(t *testing.T) *sqlitex.Pool {
	// Create temporary directory for test database
	tempDir := t.TempDir()
	dbPath := filepath.Join(tempDir, "test_guilds.db")

	// Create and initialize database pool
	pool, err := sqlitex.NewPool(dbPath, sqlitex.PoolOptions{
		Flags: sqlite.OpenReadWrite | sqlite.OpenCreate,
	})
	if err != nil {
		t.Fatalf("Failed to create test database pool: %v", err)
	}

	// Create guild tables for testing
	conn, err := pool.Take(context.Background())
	if err != nil {
		t.Fatalf("Failed to get connection: %v", err)
	}
	defer pool.Put(conn)

	err = createGuildTables(conn)
	if err != nil {
		t.Fatalf("Failed to create guild tables: %v", err)
	}

	return pool
}

// createGuildTables creates the necessary tables for guild testing
func createGuildTables(conn *sqlite.Conn) error {
	tables := []string{
		`CREATE TABLE IF NOT EXISTS guilds (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL,
			motd TEXT,
			level INTEGER DEFAULT 1,
			xp INTEGER DEFAULT 111,
			xp_needed INTEGER DEFAULT 2521,
			formed_on INTEGER DEFAULT 0
		)`,
		`CREATE TABLE IF NOT EXISTS guild_members (
			char_id INTEGER PRIMARY KEY,
			guild_id INTEGER NOT NULL,
			account_id INTEGER NOT NULL,
			recruiter_id INTEGER DEFAULT 0,
			name TEXT NOT NULL,
			guild_status INTEGER DEFAULT 0,
			points REAL DEFAULT 0.0,
			adventure_class INTEGER DEFAULT 0,
			adventure_level INTEGER DEFAULT 1,
			tradeskill_class INTEGER DEFAULT 0,
			tradeskill_level INTEGER DEFAULT 1,
			rank INTEGER DEFAULT 7,
			member_flags INTEGER DEFAULT 0,
			zone TEXT DEFAULT '',
			join_date INTEGER DEFAULT 0,
			last_login_date INTEGER DEFAULT 0,
			note TEXT DEFAULT '',
			officer_note TEXT DEFAULT '',
			recruiter_description TEXT DEFAULT '',
			recruiter_picture_data BLOB,
			recruiting_show_adventure_class INTEGER DEFAULT 0,
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_events (
			event_id INTEGER NOT NULL,
			guild_id INTEGER NOT NULL,
			date INTEGER NOT NULL,
			type INTEGER NOT NULL,
			description TEXT NOT NULL,
			locked INTEGER DEFAULT 0,
			PRIMARY KEY (event_id, guild_id),
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_ranks (
			guild_id INTEGER NOT NULL,
			rank INTEGER NOT NULL,
			name TEXT NOT NULL,
			PRIMARY KEY (guild_id, rank),
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_permissions (
			guild_id INTEGER NOT NULL,
			rank INTEGER NOT NULL,
			permission INTEGER NOT NULL,
			value INTEGER NOT NULL,
			PRIMARY KEY (guild_id, rank, permission),
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_event_filters (
			guild_id INTEGER NOT NULL,
			event_id INTEGER NOT NULL,
			category INTEGER NOT NULL,
			value INTEGER NOT NULL,
			PRIMARY KEY (guild_id, event_id, category),
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_recruiting (
			guild_id INTEGER NOT NULL,
			flag INTEGER NOT NULL,
			value INTEGER NOT NULL,
			PRIMARY KEY (guild_id, flag),
			FOREIGN KEY (guild_id) REFERENCES guilds(id)
		)`,
		`CREATE TABLE IF NOT EXISTS guild_point_history (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			char_id INTEGER NOT NULL,
			date INTEGER NOT NULL,
			modified_by TEXT NOT NULL,
			comment TEXT NOT NULL,
			points REAL NOT NULL,
			FOREIGN KEY (char_id) REFERENCES guild_members(char_id)
		)`,
	}

	for _, sql := range tables {
		err := sqlitex.ExecScript(conn, sql)
		if err != nil {
			return err
		}
	}

	return nil
}

// execSQL is a helper to execute SQL with parameters
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...interface{}) {
	conn, err := pool.Take(context.Background())
	if err != nil {
		t.Fatalf("Failed to get connection: %v", err)
	}
	defer pool.Put(conn)

	stmt := conn.Prep(query)
	defer stmt.Finalize()

	for i, arg := range args {
		switch v := arg.(type) {
		case int:
			stmt.BindInt64(i+1, int64(v))
		case int32:
			stmt.BindInt64(i+1, int64(v))
		case int64:
			stmt.BindInt64(i+1, v)
		case float64:
			stmt.BindFloat(i+1, v)
		case string:
			stmt.BindText(i+1, v)
		case []byte:
			stmt.BindBytes(i+1, v)
		case nil:
			stmt.BindNull(i + 1)
		default:
			t.Fatalf("Unsupported argument type: %T", v)
		}
	}

	_, err = stmt.Step()
	if err != nil {
		t.Fatalf("Failed to execute SQL: %v", err)
	}
}

// TestDatabaseGuildManager_LoadGuilds tests loading guilds from database
func TestDatabaseGuildManager_LoadGuilds(t *testing.T) {
	pool := createTestPool(t)
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Insert test data
	formedTime := time.Now().Unix()
	execSQL(t, pool, `INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on) 
	                   VALUES (?, ?, ?, ?, ?, ?, ?)`,
		1, "Test Guild", "Welcome!", 5, 1000, 5000, formedTime)

	// Test loading guilds
	guilds, err := dgm.LoadGuilds(ctx)
	if err != nil {
		t.Fatalf("Failed to load guilds: %v", err)
	}

	if len(guilds) != 1 {
		t.Errorf("Expected 1 guild, got %d", len(guilds))
	}

	if len(guilds) > 0 {
		guild := guilds[0]
		if guild.ID != 1 {
			t.Errorf("Expected guild ID 1, got %d", guild.ID)
		}
		if guild.Name != "Test Guild" {
			t.Errorf("Expected guild name 'Test Guild', got '%s'", guild.Name)
		}
		if guild.MOTD != "Welcome!" {
			t.Errorf("Expected MOTD 'Welcome!', got '%s'", guild.MOTD)
		}
		if guild.Level != 5 {
			t.Errorf("Expected level 5, got %d", guild.Level)
		}
	}
}

// TestDatabaseGuildManager_LoadGuildMembers tests loading guild members
func TestDatabaseGuildManager_LoadGuildMembers(t *testing.T) {
	pool := createTestPool(t)
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Insert test guild and members
	execSQL(t, pool, `INSERT INTO guilds (id, name) VALUES (?, ?)`, 1, "Test Guild")

	joinTime := time.Now().Unix()
	loginTime := time.Now().Unix()

	execSQL(t, pool, `INSERT INTO guild_members 
	                   (char_id, guild_id, account_id, name, guild_status, points, 
	                    adventure_class, adventure_level, tradeskill_class, tradeskill_level,
	                    rank, member_flags, zone, join_date, last_login_date, note, officer_note,
	                    recruiter_description, recruiter_picture_data, recruiting_show_adventure_class)
	                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		100, 1, 10, "TestPlayer", 1, 100.5, 5, 50, 2, 20, 3, 0, "Test Zone",
		joinTime, loginTime, "Player note", "Officer note", "Recruiter desc",
		[]byte{0x01, 0x02, 0x03}, 1)

	// Test loading members
	members, err := dgm.LoadGuildMembers(ctx, 1)
	if err != nil {
		t.Fatalf("Failed to load guild members: %v", err)
	}

	if len(members) != 1 {
		t.Errorf("Expected 1 member, got %d", len(members))
	}

	if len(members) > 0 {
		member := members[0]
		if member.CharacterID != 100 {
			t.Errorf("Expected character ID 100, got %d", member.CharacterID)
		}
		if member.Name != "TestPlayer" {
			t.Errorf("Expected name 'TestPlayer', got '%s'", member.Name)
		}
		if member.Points != 100.5 {
			t.Errorf("Expected points 100.5, got %f", member.Points)
		}
		if len(member.RecruiterPictureData) != 3 {
			t.Errorf("Expected picture data length 3, got %d", len(member.RecruiterPictureData))
		}
	}
}

// TestDatabaseGuildManager_SaveGuild tests saving guild data
func TestDatabaseGuildManager_SaveGuild(t *testing.T) {
	pool := createTestPool(t)
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Create a test guild
	guild := &Guild{
		id:             1,
		name:           "New Guild",
		motd:           "Test MOTD",
		level:          10,
		expCurrent:     2000,
		expToNextLevel: 8000,
		formedDate:     time.Now(),
	}

	// Save the guild
	err := dgm.SaveGuild(ctx, guild)
	if err != nil {
		t.Fatalf("Failed to save guild: %v", err)
	}

	// Load and verify
	guilds, err := dgm.LoadGuilds(ctx)
	if err != nil {
		t.Fatalf("Failed to load guilds: %v", err)
	}

	if len(guilds) != 1 {
		t.Errorf("Expected 1 guild, got %d", len(guilds))
	}

	if len(guilds) > 0 {
		loaded := guilds[0]
		if loaded.Name != "New Guild" {
			t.Errorf("Expected name 'New Guild', got '%s'", loaded.Name)
		}
		if loaded.MOTD != "Test MOTD" {
			t.Errorf("Expected MOTD 'Test MOTD', got '%s'", loaded.MOTD)
		}
		if loaded.Level != 10 {
			t.Errorf("Expected level 10, got %d", loaded.Level)
		}
	}
}

// TestDatabaseGuildManager_CreateAndDeleteGuild tests guild creation and deletion
func TestDatabaseGuildManager_CreateAndDeleteGuild(t *testing.T) {
	pool := createTestPool(t)
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Create guild data
	guildData := GuildData{
		Name:           "Delete Test Guild",
		MOTD:           "To be deleted",
		Level:          1,
		EXPCurrent:     0,
		EXPToNextLevel: 1000,
		FormedDate:     time.Now(),
	}

	// Create the guild
	guildID, err := dgm.CreateGuild(ctx, guildData)
	if err != nil {
		t.Fatalf("Failed to create guild: %v", err)
	}

	if guildID <= 0 {
		t.Errorf("Expected valid guild ID, got %d", guildID)
	}

	// Verify it exists
	guild, err := dgm.LoadGuild(ctx, guildID)
	if err != nil {
		t.Fatalf("Failed to load created guild: %v", err)
	}

	if guild.Name != "Delete Test Guild" {
		t.Errorf("Expected name 'Delete Test Guild', got '%s'", guild.Name)
	}

	// Delete the guild
	err = dgm.DeleteGuild(ctx, guildID)
	if err != nil {
		t.Fatalf("Failed to delete guild: %v", err)
	}

	// Verify it's gone
	_, err = dgm.LoadGuild(ctx, guildID)
	if err == nil {
		t.Error("Expected error loading deleted guild, got nil")
	}
}

// BenchmarkDatabaseGuildManager_LoadGuilds benchmarks loading guilds
func BenchmarkDatabaseGuildManager_LoadGuilds(b *testing.B) {
	pool := createTestPool(&testing.T{})
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Insert test data
	for i := 1; i <= 100; i++ {
		execSQL(&testing.T{}, pool,
			`INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on) 
			 VALUES (?, ?, ?, ?, ?, ?, ?)`,
			i, fmt.Sprintf("Guild %d", i), "Welcome!", i%10+1, i*100, i*1000, time.Now().Unix())
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		guilds, err := dgm.LoadGuilds(ctx)
		if err != nil {
			b.Fatalf("Failed to load guilds: %v", err)
		}
		if len(guilds) != 100 {
			b.Errorf("Expected 100 guilds, got %d", len(guilds))
		}
	}
}

// BenchmarkDatabaseGuildManager_SaveGuild benchmarks saving guild data
func BenchmarkDatabaseGuildManager_SaveGuild(b *testing.B) {
	pool := createTestPool(&testing.T{})
	defer pool.Close()

	dgm := NewDatabaseGuildManager(pool)
	ctx := context.Background()

	// Create a test guild
	guild := &Guild{
		id:             1,
		name:           "Benchmark Guild",
		motd:           "Benchmark MOTD",
		level:          50,
		expCurrent:     50000,
		expToNextLevel: 100000,
		formedDate:     time.Now(),
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		err := dgm.SaveGuild(ctx, guild)
		if err != nil {
			b.Fatalf("Failed to save guild: %v", err)
		}
	}
}
