package repositories

import (
	"testing"

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

	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

func TestContractorRepository_Create(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	contractor := &models.Contractor{
		ResidenceID: &residence.ID,
		CreatedByID: user.ID,
		Name:        "Mike's Plumbing",
		Company:     "Mike's Plumbing Co.",
		Phone:       "+1-555-1234",
		Email:       "mike@plumbing.com",
		IsActive:    true,
	}

	err := repo.Create(contractor)
	require.NoError(t, err)
	assert.NotZero(t, contractor.ID)
}

func TestContractorRepository_FindByID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Test Contractor")

	found, err := repo.FindByID(contractor.ID)
	require.NoError(t, err)
	assert.Equal(t, contractor.ID, found.ID)
	assert.Equal(t, "Test Contractor", found.Name)
}

func TestContractorRepository_FindByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	_, err := repo.FindByID(9999)
	assert.Error(t, err)
}

func TestContractorRepository_FindByID_InactiveNotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Inactive Contractor")

	// Soft-delete
	db.Model(&models.Contractor{}).Where("id = ?", contractor.ID).Update("is_active", false)

	_, err := repo.FindByID(contractor.ID)
	assert.Error(t, err)
}

func TestContractorRepository_FindByResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	otherResidence := testutil.CreateTestResidence(t, db, user.ID, "Other House")

	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor A")
	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor B")
	testutil.CreateTestContractor(t, db, otherResidence.ID, user.ID, "Other Contractor")

	contractors, err := repo.FindByResidence(residence.ID)
	require.NoError(t, err)
	assert.Len(t, contractors, 2)
}

func TestContractorRepository_FindByResidence_ExcludesInactive(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	active := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Active Contractor")
	inactive := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Inactive Contractor")
	db.Model(&models.Contractor{}).Where("id = ?", inactive.ID).Update("is_active", false)

	contractors, err := repo.FindByResidence(residence.ID)
	require.NoError(t, err)
	assert.Len(t, contractors, 1)
	assert.Equal(t, active.ID, contractors[0].ID)
}

func TestContractorRepository_FindByUser_WithResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Residence Contractor")

	contractors, err := repo.FindByUser(user.ID, []uint{residence.ID})
	require.NoError(t, err)
	assert.Len(t, contractors, 1)
	assert.Equal(t, "Residence Contractor", contractors[0].Name)
}

func TestContractorRepository_FindByUser_NoResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	// Personal contractor with no residence
	personal := &models.Contractor{
		ResidenceID: nil,
		CreatedByID: user.ID,
		Name:        "Personal Contractor",
		IsActive:    true,
	}
	err := db.Create(personal).Error
	require.NoError(t, err)

	contractors, err := repo.FindByUser(user.ID, []uint{})
	require.NoError(t, err)
	assert.Len(t, contractors, 1)
	assert.Equal(t, "Personal Contractor", contractors[0].Name)
}

func TestContractorRepository_Update(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Original Name")

	contractor.Name = "Updated Name"
	contractor.Phone = "+1-555-9999"
	err := repo.Update(contractor)
	require.NoError(t, err)

	found, err := repo.FindByID(contractor.ID)
	require.NoError(t, err)
	assert.Equal(t, "Updated Name", found.Name)
	assert.Equal(t, "+1-555-9999", found.Phone)
}

func TestContractorRepository_Delete(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "To Delete")

	err := repo.Delete(contractor.ID)
	require.NoError(t, err)

	// Should not be found (soft delete)
	_, err = repo.FindByID(contractor.ID)
	assert.Error(t, err)
}

func TestContractorRepository_CountByResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor 1")
	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor 2")

	count, err := repo.CountByResidence(residence.ID)
	require.NoError(t, err)
	assert.Equal(t, int64(2), count)
}

func TestContractorRepository_CountByResidenceIDs(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1")
	r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2")

	testutil.CreateTestContractor(t, db, r1.ID, user.ID, "Contractor A")
	testutil.CreateTestContractor(t, db, r2.ID, user.ID, "Contractor B")
	testutil.CreateTestContractor(t, db, r2.ID, user.ID, "Contractor C")

	count, err := repo.CountByResidenceIDs([]uint{r1.ID, r2.ID})
	require.NoError(t, err)
	assert.Equal(t, int64(3), count)
}

func TestContractorRepository_CountByResidenceIDs_Empty(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	count, err := repo.CountByResidenceIDs([]uint{})
	require.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

func TestContractorRepository_SetSpecialties(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Skilled Contractor")

	// Get seeded specialties
	var specialties []models.ContractorSpecialty
	err := db.Find(&specialties).Error
	require.NoError(t, err)
	require.GreaterOrEqual(t, len(specialties), 2)

	// Set specialties
	err = repo.SetSpecialties(contractor.ID, []uint{specialties[0].ID, specialties[1].ID})
	require.NoError(t, err)

	// Verify via FindByID
	found, err := repo.FindByID(contractor.ID)
	require.NoError(t, err)
	assert.Len(t, found.Specialties, 2)
}

func TestContractorRepository_SetSpecialties_ClearsExisting(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Skilled Contractor")

	var specialties []models.ContractorSpecialty
	err := db.Find(&specialties).Error
	require.NoError(t, err)
	require.GreaterOrEqual(t, len(specialties), 2)

	// Set initial specialties
	err = repo.SetSpecialties(contractor.ID, []uint{specialties[0].ID, specialties[1].ID})
	require.NoError(t, err)

	// Clear all specialties
	err = repo.SetSpecialties(contractor.ID, []uint{})
	require.NoError(t, err)

	found, err := repo.FindByID(contractor.ID)
	require.NoError(t, err)
	assert.Len(t, found.Specialties, 0)
}

func TestContractorRepository_GetAllSpecialties(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewContractorRepository(db)

	specialties, err := repo.GetAllSpecialties()
	require.NoError(t, err)
	assert.GreaterOrEqual(t, len(specialties), 4) // Seeded 4 specialties
}

func TestContractorRepository_FindSpecialtyByID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	repo := NewContractorRepository(db)

	var seeded models.ContractorSpecialty
	err := db.First(&seeded).Error
	require.NoError(t, err)

	found, err := repo.FindSpecialtyByID(seeded.ID)
	require.NoError(t, err)
	assert.Equal(t, seeded.Name, found.Name)
}

func TestContractorRepository_FindSpecialtyByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	_, err := repo.FindSpecialtyByID(9999)
	assert.Error(t, err)
}

func TestContractorRepository_GetTasksForContractor(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Task Contractor")

	// Create tasks linked to contractor
	task1 := &models.Task{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "Task 1",
		ContractorID: &contractor.ID,
		Version:      1,
	}
	err := db.Create(task1).Error
	require.NoError(t, err)

	task2 := &models.Task{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "Task 2",
		ContractorID: &contractor.ID,
		Version:      1,
	}
	err = db.Create(task2).Error
	require.NoError(t, err)

	tasks, err := repo.GetTasksForContractor(contractor.ID)
	require.NoError(t, err)
	assert.Len(t, tasks, 2)
}

func TestContractorRepository_FindByResidence_FavoritesFirst(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewContractorRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	// Create non-favorite first (alphabetically first)
	testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Alpha Contractor")

	// Create favorite
	fav := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Zeta Contractor")
	db.Model(&models.Contractor{}).Where("id = ?", fav.ID).Update("is_favorite", true)

	contractors, err := repo.FindByResidence(residence.ID)
	require.NoError(t, err)
	assert.Len(t, contractors, 2)
	// Favorite should be first
	assert.Equal(t, fav.ID, contractors[0].ID)
}
