package models

import (
	"bamort/database"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func setupTestDB(t *testing.T) {
	database.SetupTestDB()

	// Migrate structures
	err := MigrateStructure()
	require.NoError(t, err, "Failed to migrate database structure")
}

func createTestSkill(name string) *Skill {
	return &Skill{
		GameSystem:       "midgard",
		Name:             name,
		Beschreibung:     "Test skill description",
		Category:         "Körper",
		Difficulty:       "Normal",
		Initialwert:      5,
		Bonuseigenschaft: "Gs",
		Improvable:       true,
		InnateSkill:      false,
	}
}

// =============================================================================
// Tests for SkFertigkeit struct
// =============================================================================

func TestSkFertigkeit_TableName(t *testing.T) {
	skill := SkFertigkeit{}
	expected := "skill_skills"
	actual := skill.TableName()
	assert.Equal(t, expected, actual)
}

func TestSkFertigkeit_GetSkillByName_Success(t *testing.T) {
	//setupTestDB(t)
	database.SetupTestDB(true)

	// Create a test skill in the database
	testSkill := createTestSkill("Athletika")
	err := testSkill.Create()
	require.NoError(t, err)

	// Create SkFertigkeit with the same name
	skFertigkeit := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "Athletika",
			},
		},
	}

	// Test GetSkillByName
	result := skFertigkeit.GetSkillByName()

	assert.NotNil(t, result)
	assert.Equal(t, "Athletika", result.Name)
	assert.Equal(t, "Körper", result.Category)
	assert.Equal(t, "Normal", result.Difficulty)
	assert.Equal(t, 5, result.Initialwert)
}

func TestSkFertigkeit_GetSkillByName_NotFound(t *testing.T) {
	setupTestDB(t)

	// Create SkFertigkeit with non-existent name
	skFertigkeit := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "NonExistentSkill",
			},
		},
	}

	// Test GetSkillByName
	result := skFertigkeit.GetSkillByName()

	assert.Nil(t, result)
}

func TestSkFertigkeit_GetCategory_AlreadySet(t *testing.T) {
	skFertigkeit := SkFertigkeit{
		Category: "AlreadySetCategory",
	}

	result := skFertigkeit.GetCategory()
	assert.Equal(t, "AlreadySetCategory", result)
}

func TODOT_estSkFertigkeit_GetCategory_FromDatabase(t *testing.T) {
	setupTestDB(t)

	// Create a test skill in the database
	testSkill := createTestSkill("Schwimmen")
	testSkill.Category = "Körper"
	err := testSkill.Create()
	require.NoError(t, err)

	// Create SkFertigkeit without category but with name
	skFertigkeit := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "Schwimmen",
			},
		},
		Category: "", // Empty category
	}

	result := skFertigkeit.GetCategory()

	assert.Equal(t, "Körper", result)
	assert.Equal(t, "Körper", skFertigkeit.Category) // Should be set on the object
}

func TestSkFertigkeit_GetCategory_NotFoundInDatabase(t *testing.T) {
	setupTestDB(t)

	skFertigkeit := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "UnknownSkill",
			},
		},
		Category: "", // Empty category
	}

	result := skFertigkeit.GetCategory()

	assert.Equal(t, "Unkategorisiert", result)
}

func TestSkFertigkeit_StructTags(t *testing.T) {
	// Test that the struct has the expected JSON tags
	skill := SkFertigkeit{
		Beschreibung:    "Test description",
		Fertigkeitswert: 10,
		Bonus:           2,
		Pp:              5,
		Bemerkung:       "Test comment",
		Improvable:      true,
		Category:        "TestCategory",
	}

	// Verify struct fields exist and can be set
	assert.Equal(t, "Test description", skill.Beschreibung)
	assert.Equal(t, 10, skill.Fertigkeitswert)
	assert.Equal(t, 2, skill.Bonus)
	assert.Equal(t, 5, skill.Pp)
	assert.Equal(t, "Test comment", skill.Bemerkung)
	assert.True(t, skill.Improvable)
	assert.Equal(t, "TestCategory", skill.Category)
}

// =============================================================================
// Tests for SkWaffenfertigkeit struct
// =============================================================================

func TestSkWaffenfertigkeit_TableName(t *testing.T) {
	weaponSkill := SkWaffenfertigkeit{}
	expected := "skill_weaponskills"
	actual := weaponSkill.TableName()
	assert.Equal(t, expected, actual)
}

func TestSkWaffenfertigkeit_Inheritance(t *testing.T) {
	// Test that SkWaffenfertigkeit properly inherits from SkFertigkeit
	weaponSkill := SkWaffenfertigkeit{
		SkFertigkeit: SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "Einhandschwerter",
				},
				CharacterID: 1,
				UserID:      1,
			},
			Beschreibung:    "Sword fighting skill",
			Fertigkeitswert: 12,
			Bonus:           3,
			Pp:              8,
			Bemerkung:       "Advanced sword training",
			Improvable:      true,
			Category:        "Kampf",
		},
	}

	// Verify all inherited fields are accessible
	assert.Equal(t, "Einhandschwerter", weaponSkill.Name)
	assert.Equal(t, uint(1), weaponSkill.CharacterID)
	assert.Equal(t, uint(1), weaponSkill.UserID)
	assert.Equal(t, "Sword fighting skill", weaponSkill.Beschreibung)
	assert.Equal(t, 12, weaponSkill.Fertigkeitswert)
	assert.Equal(t, 3, weaponSkill.Bonus)
	assert.Equal(t, 8, weaponSkill.Pp)
	assert.Equal(t, "Advanced sword training", weaponSkill.Bemerkung)
	assert.True(t, weaponSkill.Improvable)
	assert.Equal(t, "Kampf", weaponSkill.Category)
}

func TestSkWaffenfertigkeit_InheritedMethods(t *testing.T) {
	setupTestDB(t)

	// Test inherited methods work - use existing Stichwaffen weapon skill
	weaponSkill := SkWaffenfertigkeit{
		SkFertigkeit: SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "Stichwaffen",
				},
			},
		},
	}

	// Test GetSkillByName (inherited from SkFertigkeit)
	result := weaponSkill.GetSkillByName()
	assert.NotNil(t, result)
	assert.Equal(t, "Stichwaffen", result.Name)

	// Test GetCategory (inherited from SkFertigkeit)
	category := weaponSkill.GetCategory()
	assert.Equal(t, "", category)
}

// =============================================================================
// Tests for SkAngeboreneFertigkeit struct
// =============================================================================

func TestSkAngeboreneFertigkeit_Inheritance(t *testing.T) {
	// Test that SkAngeboreneFertigkeit properly inherits from SkFertigkeit
	innateSkill := SkAngeboreneFertigkeit{
		SkFertigkeit: SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "Nachtsicht",
				},
				CharacterID: 2,
				UserID:      2,
			},
			Beschreibung:    "Natural night vision",
			Fertigkeitswert: 15,
			Bonus:           0,
			Pp:              0,
			Bemerkung:       "Racial ability",
			Improvable:      false, // Innate skills typically not improvable
			Category:        "Angeboren",
		},
	}

	// Verify all inherited fields are accessible
	assert.Equal(t, "Nachtsicht", innateSkill.Name)
	assert.Equal(t, uint(2), innateSkill.CharacterID)
	assert.Equal(t, uint(2), innateSkill.UserID)
	assert.Equal(t, "Natural night vision", innateSkill.Beschreibung)
	assert.Equal(t, 15, innateSkill.Fertigkeitswert)
	assert.Equal(t, 0, innateSkill.Bonus)
	assert.Equal(t, 0, innateSkill.Pp)
	assert.Equal(t, "Racial ability", innateSkill.Bemerkung)
	assert.False(t, innateSkill.Improvable)
	assert.Equal(t, "Angeboren", innateSkill.Category)
}

// =============================================================================
// Tests for SkZauber struct
// =============================================================================

func TestSkZauber_TableName(t *testing.T) {
	spell := SkZauber{}
	expected := "skill_spells"
	actual := spell.TableName()
	assert.Equal(t, expected, actual)
}

func TestSkZauber_StructFields(t *testing.T) {
	spell := SkZauber{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				ID:   1,
				Name: "Feuerkugel",
			},
			CharacterID: 3,
			UserID:      3,
		},
		Beschreibung: "Creates a ball of fire",
		Bonus:        5,
		Quelle:       "Arkanum",
	}

	// Verify all fields are accessible and correct
	assert.Equal(t, uint(1), spell.ID)
	assert.Equal(t, "Feuerkugel", spell.Name)
	assert.Equal(t, uint(3), spell.CharacterID)
	assert.Equal(t, uint(3), spell.UserID)
	assert.Equal(t, "Creates a ball of fire", spell.Beschreibung)
	assert.Equal(t, 5, spell.Bonus)
	assert.Equal(t, "Arkanum", spell.Quelle)
}

func TestSkZauber_StructTags(t *testing.T) {
	// Test that the struct has the expected JSON tags by creating a spell
	spell := SkZauber{
		Beschreibung: "Lightning spell",
		Bonus:        3,
		Quelle:       "Elementar",
	}

	// Verify struct fields exist and can be set
	assert.Equal(t, "Lightning spell", spell.Beschreibung)
	assert.Equal(t, 3, spell.Bonus)
	assert.Equal(t, "Elementar", spell.Quelle)
}

// =============================================================================
// Integration Tests
// =============================================================================

func TestSkillStructures_WithDatabase(t *testing.T) {
	setupTestDB(t)

	// Create test skills in database
	testSkill := createTestSkill("Klettern")
	testSkill.Category = "Alltag"
	err := testSkill.Create()
	require.NoError(t, err)

	weaponSkillData := WeaponSkill{
		Skill: Skill{
			GameSystem:       "midgard",
			Name:             "Bögen",
			Beschreibung:     "Bow skills",
			Category:         "Fernkampf",
			Difficulty:       "Normal",
			Initialwert:      5,
			Bonuseigenschaft: "Gs",
			Improvable:       true,
			InnateSkill:      false,
		},
	}
	err = weaponSkillData.Create()
	require.NoError(t, err)

	// Test SkFertigkeit with database interaction
	t.Run("SkFertigkeit Database Integration", func(t *testing.T) {
		skill := SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "Klettern",
				},
				CharacterID: 1,
				UserID:      1,
			},
			Fertigkeitswert: 8,
			Improvable:      true,
		}

		// Test GetSkillByName
		gsSkill := skill.GetSkillByName()
		assert.NotNil(t, gsSkill)
		assert.Equal(t, "Klettern", gsSkill.Name)
		assert.Equal(t, "Alltag", gsSkill.Category)

		// Test GetCategory
		category := skill.GetCategory()
		assert.Equal(t, "Alltag", category)
		assert.Equal(t, "Alltag", skill.Category) // Should be set on object
	})

	// Test SkWaffenfertigkeit with database interaction
	/*
		t.Run("SkWaffenfertigkeit Database Integration", func(t *testing.T) {
			weaponSkill := SkWaffenfertigkeit{
				SkFertigkeit: SkFertigkeit{
					BamortCharTrait: BamortCharTrait{
						BamortBase: BamortBase{
							Name: "Bögen",
						},
						CharacterID: 2,
						UserID:      2,
					},
					Fertigkeitswert: 10,
					Improvable:      true,
				},
			}

			// Test inherited methods
			gsSkill := weaponSkill.GetSkillByName()
			assert.NotNil(t, gsSkill)
			assert.Equal(t, "Bögen", gsSkill.Name)
			assert.Equal(t, "Fernkampf", gsSkill.Category)

			category := weaponSkill.GetCategory()
			assert.Equal(t, "Fernkampf", category)
		})
	*/
}

func TestTableNames_Consistency(t *testing.T) {
	// Test that all table names follow the expected pattern
	skill := SkFertigkeit{}
	weaponSkill := SkWaffenfertigkeit{}
	spell := SkZauber{}

	assert.Equal(t, "skill_skills", skill.TableName())
	assert.Equal(t, "skill_weaponskills", weaponSkill.TableName())
	assert.Equal(t, "skill_spells", spell.TableName())

	// All table names should start with "skill_"
	assert.Contains(t, skill.TableName(), "skill_")
	assert.Contains(t, weaponSkill.TableName(), "skill_")
	assert.Contains(t, spell.TableName(), "skill_")
}

func TestSkFertigkeit_EdgeCases(t *testing.T) {
	setupTestDB(t)

	t.Run("GetSkillByName with empty name", func(t *testing.T) {
		skill := SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "",
				},
			},
		}

		result := skill.GetSkillByName()
		assert.Nil(t, result)
	})

	t.Run("GetCategory with nil database connection", func(t *testing.T) {
		// This tests the behavior when the database lookup fails
		skill := SkFertigkeit{
			BamortCharTrait: BamortCharTrait{
				BamortBase: BamortBase{
					Name: "NonExistentSkillForSure",
				},
			},
			Category: "",
		}

		result := skill.GetCategory()
		assert.Equal(t, "Unkategorisiert", result)
	})

	t.Run("GetCategory with already set category", func(t *testing.T) {
		skill := SkFertigkeit{
			Category: "PresetCategory",
		}

		result := skill.GetCategory()
		assert.Equal(t, "PresetCategory", result)
		// The object's Category field should remain unchanged
		assert.Equal(t, "PresetCategory", skill.Category)
	})
}

// =============================================================================
// Benchmark Tests (Optional)
// =============================================================================

func BenchmarkSkFertigkeit_GetSkillByName(b *testing.B) {
	database.SetupTestDB()
	err := MigrateStructure()
	if err != nil {
		b.Fatal("Failed to migrate structure:", err)
	}

	// Create test skill
	testSkill := createTestSkill("BenchmarkSkill")
	err = testSkill.Create()
	if err != nil {
		b.Fatal("Failed to create test skill:", err)
	}

	skill := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "BenchmarkSkill",
			},
		},
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		skill.GetSkillByName()
	}
}

func BenchmarkSkFertigkeit_GetCategory(b *testing.B) {
	database.SetupTestDB()
	err := MigrateStructure()
	if err != nil {
		b.Fatal("Failed to migrate structure:", err)
	}

	// Create test skill
	testSkill := createTestSkill("BenchmarkCategorySkill")
	testSkill.Category = "BenchmarkCategory"
	err = testSkill.Create()
	if err != nil {
		b.Fatal("Failed to create test skill:", err)
	}

	skill := SkFertigkeit{
		BamortCharTrait: BamortCharTrait{
			BamortBase: BamortBase{
				Name: "BenchmarkCategorySkill",
			},
		},
		Category: "", // Force database lookup
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Reset category for each iteration to force database lookup
		skill.Category = ""
		skill.GetCategory()
	}
}
