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 TestDocumentRepository_Create(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	doc := &models.Document{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "HVAC Warranty",
		DocumentType: models.DocumentTypeWarranty,
		FileURL:      "https://example.com/hvac.pdf",
		IsActive:     true,
	}

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

func TestDocumentRepository_FindByID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	found, err := repo.FindByID(doc.ID)
	require.NoError(t, err)
	assert.Equal(t, doc.ID, found.ID)
	assert.Equal(t, "Test Doc", found.Title)
}

func TestDocumentRepository_FindByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

func TestDocumentRepository_FindByID_InactiveNotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	db.Model(&models.Document{}).Where("id = ?", doc.ID).Update("is_active", false)

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

func TestDocumentRepository_FindByResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(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.CreateTestDocument(t, db, residence.ID, user.ID, "Doc A")
	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc B")
	testutil.CreateTestDocument(t, db, otherResidence.ID, user.ID, "Other Doc")

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

func TestDocumentRepository_FindByResidence_ExcludesInactive(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

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

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

func TestDocumentRepository_FindByUser(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(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.CreateTestDocument(t, db, r1.ID, user.ID, "Doc 1")
	testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc 2")

	docs, err := repo.FindByUser([]uint{r1.ID, r2.ID})
	require.NoError(t, err)
	assert.Len(t, docs, 2)
}

func TestDocumentRepository_Update(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	doc.Title = "Updated Title"
	doc.Description = "Updated description"
	err := repo.Update(doc)
	require.NoError(t, err)

	found, err := repo.FindByID(doc.ID)
	require.NoError(t, err)
	assert.Equal(t, "Updated Title", found.Title)
	assert.Equal(t, "Updated description", found.Description)
}

func TestDocumentRepository_Delete(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

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

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

func TestDocumentRepository_ActivateDeactivate(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// Deactivate
	err := repo.Deactivate(doc.ID)
	require.NoError(t, err)

	_, err = repo.FindByID(doc.ID)
	assert.Error(t, err) // Not found when inactive

	// Activate
	err = repo.Activate(doc.ID)
	require.NoError(t, err)

	found, err := repo.FindByID(doc.ID)
	require.NoError(t, err)
	assert.Equal(t, doc.ID, found.ID)
}

func TestDocumentRepository_CountByResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 1")
	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 2")
	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 3")

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

func TestDocumentRepository_CountByResidenceIDs(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(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.CreateTestDocument(t, db, r1.ID, user.ID, "Doc A")
	testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc B")
	testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc C")

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

func TestDocumentRepository_CountByResidenceIDs_Empty(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

func TestDocumentRepository_DocumentImages(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// Create image
	img := &models.DocumentImage{
		DocumentID: doc.ID,
		ImageURL:   "https://example.com/img1.jpg",
		Caption:    "Front page",
	}
	err := repo.CreateDocumentImage(img)
	require.NoError(t, err)
	assert.NotZero(t, img.ID)

	// Find image by ID
	found, err := repo.FindImageByID(img.ID)
	require.NoError(t, err)
	assert.Equal(t, "https://example.com/img1.jpg", found.ImageURL)
	assert.Equal(t, "Front page", found.Caption)

	// Delete single image
	err = repo.DeleteDocumentImage(img.ID)
	require.NoError(t, err)

	_, err = repo.FindImageByID(img.ID)
	assert.Error(t, err)
}

func TestDocumentRepository_DeleteDocumentImages(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// Create multiple images
	for i := 0; i < 3; i++ {
		img := &models.DocumentImage{
			DocumentID: doc.ID,
			ImageURL:   "https://example.com/img.jpg",
		}
		err := repo.CreateDocumentImage(img)
		require.NoError(t, err)
	}

	// Delete all images for document
	err := repo.DeleteDocumentImages(doc.ID)
	require.NoError(t, err)

	// Verify all deleted
	var count int64
	db.Model(&models.DocumentImage{}).Where("document_id = ?", doc.ID).Count(&count)
	assert.Equal(t, int64(0), count)
}

func TestDocumentRepository_FindByIDIncludingInactive(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// Deactivate
	db.Model(&models.Document{}).Where("id = ?", doc.ID).Update("is_active", false)

	// FindByID should not find it
	_, err := repo.FindByID(doc.ID)
	assert.Error(t, err)

	// FindByIDIncludingInactive should find it
	var found models.Document
	err = repo.FindByIDIncludingInactive(doc.ID, &found)
	require.NoError(t, err)
	assert.Equal(t, doc.ID, found.ID)
}

func TestDocumentRepository_FindByUserFiltered_ByType(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// Create general document
	generalDoc := &models.Document{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "General Doc",
		DocumentType: models.DocumentTypeGeneral,
		FileURL:      "https://example.com/gen.pdf",
		IsActive:     true,
	}
	err := db.Create(generalDoc).Error
	require.NoError(t, err)

	// Create warranty document
	warrantyDoc := &models.Document{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "Warranty Doc",
		DocumentType: models.DocumentTypeWarranty,
		FileURL:      "https://example.com/warranty.pdf",
		IsActive:     true,
	}
	err = db.Create(warrantyDoc).Error
	require.NoError(t, err)

	// Filter by warranty type
	filter := &DocumentFilter{
		DocumentType: string(models.DocumentTypeWarranty),
	}
	docs, err := repo.FindByUserFiltered([]uint{residence.ID}, filter)
	require.NoError(t, err)
	assert.Len(t, docs, 1)
	assert.Equal(t, "Warranty Doc", docs[0].Title)
}

func TestDocumentRepository_FindByUserFiltered_NilFilter(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 1")
	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 2")

	docs, err := repo.FindByUserFiltered([]uint{residence.ID}, nil)
	require.NoError(t, err)
	assert.Len(t, docs, 2)
}

func TestDocumentRepository_FindWarranties(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewDocumentRepository(db)

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

	// General doc (should not be returned)
	testutil.CreateTestDocument(t, db, residence.ID, user.ID, "General Doc")

	// Warranty doc
	warranty := &models.Document{
		ResidenceID:  residence.ID,
		CreatedByID:  user.ID,
		Title:        "Warranty",
		DocumentType: models.DocumentTypeWarranty,
		FileURL:      "https://example.com/warranty.pdf",
		IsActive:     true,
	}
	err := db.Create(warranty).Error
	require.NoError(t, err)

	docs, err := repo.FindWarranties([]uint{residence.ID})
	require.NoError(t, err)
	assert.Len(t, docs, 1)
	assert.Equal(t, "Warranty", docs[0].Title)
}
