package services

import (
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/dto/requests"
	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/repositories"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

func setupResidenceService(t *testing.T) (*ResidenceService, *repositories.ResidenceRepository, *repositories.UserRepository) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)
	return service, residenceRepo, userRepo
}

func TestResidenceService_CreateResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	req := &requests.CreateResidenceRequest{
		Name:          "Test House",
		StreetAddress: "123 Main St",
		City:          "Austin",
		StateProvince: "TX",
		PostalCode:    "78701",
	}

	resp, err := service.CreateResidence(req, user.ID)
	require.NoError(t, err)
	assert.NotNil(t, resp)
	assert.Equal(t, "Test House", resp.Data.Name)
	assert.Equal(t, "123 Main St", resp.Data.StreetAddress)
	assert.Equal(t, "Austin", resp.Data.City)
	assert.Equal(t, "TX", resp.Data.StateProvince)
	assert.Equal(t, "USA", resp.Data.Country) // Default country
	assert.True(t, resp.Data.IsPrimary)       // Default is_primary
}

func TestResidenceService_CreateResidence_WithOptionalFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	bedrooms := 3
	bathrooms := decimal.NewFromFloat(2.5)
	sqft := 2000
	isPrimary := false

	req := &requests.CreateResidenceRequest{
		Name:          "Test House",
		StreetAddress: "123 Main St",
		City:          "Austin",
		StateProvince: "TX",
		PostalCode:    "78701",
		Country:       "Canada",
		Bedrooms:      &bedrooms,
		Bathrooms:     &bathrooms,
		SquareFootage: &sqft,
		IsPrimary:     &isPrimary,
	}

	resp, err := service.CreateResidence(req, user.ID)
	require.NoError(t, err)
	assert.Equal(t, "Canada", resp.Data.Country)
	assert.Equal(t, 3, *resp.Data.Bedrooms)
	assert.True(t, resp.Data.Bathrooms.Equal(decimal.NewFromFloat(2.5)))
	assert.Equal(t, 2000, *resp.Data.SquareFootage)
	// First residence defaults to primary regardless of request
	assert.True(t, resp.Data.IsPrimary)
}

func TestResidenceService_GetResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetResidence(residence.ID, user.ID, time.Now())
	require.NoError(t, err)
	assert.Equal(t, residence.ID, resp.ID)
	assert.Equal(t, "Test House", resp.Name)
}

func TestResidenceService_GetResidence_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GetResidence(residence.ID, otherUser.ID, time.Now())
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

func TestResidenceService_GetResidence_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GetResidence(9999, user.ID, time.Now())
	assert.Error(t, err)
}

func TestResidenceService_ListResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.ListResidences(user.ID)
	require.NoError(t, err)
	assert.Len(t, resp, 2)
}

func TestResidenceService_UpdateResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Original Name")

	newName := "Updated Name"
	newCity := "Dallas"
	req := &requests.UpdateResidenceRequest{
		Name: &newName,
		City: &newCity,
	}

	resp, err := service.UpdateResidence(residence.ID, user.ID, req)
	require.NoError(t, err)
	assert.Equal(t, "Updated Name", resp.Data.Name)
	assert.Equal(t, "Dallas", resp.Data.City)
}

func TestResidenceService_UpdateResidence_NotOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Share with user
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	newName := "Updated"
	req := &requests.UpdateResidenceRequest{Name: &newName}

	_, err := service.UpdateResidence(residence.ID, sharedUser.ID, req)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.not_residence_owner")
}

func TestResidenceService_DeleteResidence(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.DeleteResidence(residence.ID, user.ID)
	require.NoError(t, err)

	// Should not be found
	_, err = service.GetResidence(residence.ID, user.ID, time.Now())
	assert.Error(t, err)
}

func TestResidenceService_DeleteResidence_NotOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	_, err := service.DeleteResidence(residence.ID, sharedUser.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.not_residence_owner")
}

func TestResidenceService_GenerateShareCode(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GenerateShareCode(residence.ID, user.ID, 24)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ShareCode.Code)
	assert.Len(t, resp.ShareCode.Code, 6)
}

func TestResidenceService_JoinWithCode(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	newUser := testutil.CreateTestUser(t, db, "newuser", "new@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")

	// Generate share code
	shareResp, err := service.GenerateShareCode(residence.ID, owner.ID, 24)
	require.NoError(t, err)

	// Join with code
	joinResp, err := service.JoinWithCode(shareResp.ShareCode.Code, newUser.ID)
	require.NoError(t, err)
	assert.Equal(t, residence.ID, joinResp.Residence.ID)

	// Verify access
	hasAccess, _ := residenceRepo.HasAccess(residence.ID, newUser.ID)
	assert.True(t, hasAccess)
}

func TestResidenceService_JoinWithCode_AlreadyMember(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	shareResp, _ := service.GenerateShareCode(residence.ID, owner.ID, 24)

	// Owner tries to join their own residence
	_, err := service.JoinWithCode(shareResp.ShareCode.Code, owner.ID)
	testutil.AssertAppError(t, err, http.StatusConflict, "error.user_already_member")
}

func TestResidenceService_GetResidenceUsers(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	user1 := testutil.CreateTestUser(t, db, "user1", "user1@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
	residenceRepo.AddUser(residence.ID, user1.ID)

	users, err := service.GetResidenceUsers(residence.ID, owner.ID)
	require.NoError(t, err)
	assert.Len(t, users, 2) // owner + shared user
}

func TestResidenceService_RemoveUser(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
	residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	err := service.RemoveUser(residence.ID, sharedUser.ID, owner.ID)
	require.NoError(t, err)

	hasAccess, _ := residenceRepo.HasAccess(residence.ID, sharedUser.ID)
	assert.False(t, hasAccess)
}

func TestResidenceService_RemoveUser_CannotRemoveOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	err := service.RemoveUser(residence.ID, owner.ID, owner.ID)
	testutil.AssertAppError(t, err, http.StatusBadRequest, "error.cannot_remove_owner")
}

// setupResidenceServiceWithSubscription creates a ResidenceService wired with a
// SubscriptionService, enabling tier limit enforcement in tests.
func setupResidenceServiceWithSubscription(t *testing.T) (*ResidenceService, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)
	documentRepo := repositories.NewDocumentRepository(db)
	subscriptionRepo := repositories.NewSubscriptionRepository(db)

	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)
	subscriptionService := NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo)
	service.SetSubscriptionService(subscriptionService)

	return service, db
}

func TestCreateResidence_FreeTier_EnforcesLimit(t *testing.T) {
	service, db := setupResidenceServiceWithSubscription(t)

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

	// Enable global limitations
	db.Where("1=1").Delete(&models.SubscriptionSettings{})
	err := db.Create(&models.SubscriptionSettings{EnableLimitations: true}).Error
	require.NoError(t, err)

	// Set free tier limit to 1 property
	one := 1
	db.Where("tier = ?", models.TierFree).Delete(&models.TierLimits{})
	err = db.Create(&models.TierLimits{
		Tier:            models.TierFree,
		PropertiesLimit: &one,
	}).Error
	require.NoError(t, err)

	// Ensure user has a free-tier subscription record
	subscriptionRepo := repositories.NewSubscriptionRepository(db)
	_, err = subscriptionRepo.GetOrCreate(owner.ID)
	require.NoError(t, err)

	// First residence should succeed (under the limit)
	req := &requests.CreateResidenceRequest{
		Name:          "First House",
		StreetAddress: "1 Main St",
		City:          "Austin",
		StateProvince: "TX",
		PostalCode:    "78701",
	}
	resp, err := service.CreateResidence(req, owner.ID)
	require.NoError(t, err)
	assert.Equal(t, "First House", resp.Data.Name)

	// Second residence should be rejected (at the limit)
	req2 := &requests.CreateResidenceRequest{
		Name:          "Second House",
		StreetAddress: "2 Main St",
		City:          "Austin",
		StateProvince: "TX",
		PostalCode:    "78702",
	}
	_, err = service.CreateResidence(req2, owner.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.properties_limit_exceeded")
}

func TestCreateResidence_ProTier_AllowsMore(t *testing.T) {
	service, db := setupResidenceServiceWithSubscription(t)

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

	// Enable global limitations
	db.Where("1=1").Delete(&models.SubscriptionSettings{})
	err := db.Create(&models.SubscriptionSettings{EnableLimitations: true}).Error
	require.NoError(t, err)

	// Set free tier limit to 1 property (pro is unlimited by default: nil limits)
	one := 1
	db.Where("tier = ?", models.TierFree).Delete(&models.TierLimits{})
	err = db.Create(&models.TierLimits{
		Tier:            models.TierFree,
		PropertiesLimit: &one,
	}).Error
	require.NoError(t, err)

	// Create a pro-tier subscription for the user
	subscriptionRepo := repositories.NewSubscriptionRepository(db)
	sub, err := subscriptionRepo.GetOrCreate(owner.ID)
	require.NoError(t, err)

	// Upgrade to Pro with a future expiration
	future := time.Now().UTC().Add(30 * 24 * time.Hour)
	sub.Tier = models.TierPro
	sub.ExpiresAt = &future
	sub.SubscribedAt = ptrTime(time.Now().UTC())
	err = subscriptionRepo.Update(sub)
	require.NoError(t, err)

	// Create multiple residences — all should succeed for Pro users
	for i := 1; i <= 3; i++ {
		req := &requests.CreateResidenceRequest{
			Name:          fmt.Sprintf("House %d", i),
			StreetAddress: fmt.Sprintf("%d Main St", i),
			City:          "Austin",
			StateProvince: "TX",
			PostalCode:    "78701",
		}
		resp, err := service.CreateResidence(req, owner.ID)
		require.NoError(t, err, "Pro user should be able to create residence %d", i)
		assert.Equal(t, fmt.Sprintf("House %d", i), resp.Data.Name)
	}
}

// ptrTime returns a pointer to the given time.
func ptrTime(t time.Time) *time.Time {
	return &t
}

// === GetMyResidences ===

func TestResidenceService_GetMyResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetMyResidences(user.ID, time.Now())
	require.NoError(t, err)
	assert.Len(t, resp.Residences, 2)
}

func TestResidenceService_GetMyResidences_NoResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetMyResidences(user.ID, time.Now())
	require.NoError(t, err)
	assert.Empty(t, resp.Residences)
}

// === GetSummary ===

func TestResidenceService_GetSummary(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetSummary(user.ID, time.Now())
	require.NoError(t, err)
	assert.Equal(t, 2, resp.TotalResidences)
}

func TestResidenceService_GetSummary_NoResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetSummary(user.ID, time.Now())
	require.NoError(t, err)
	assert.Equal(t, 0, resp.TotalResidences)
}

// === GetShareCode ===

func TestResidenceService_GetShareCode_NoActiveCode(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetShareCode(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Nil(t, resp) // No active code
}

func TestResidenceService_GetShareCode_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GetShareCode(residence.ID, other.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

// === GenerateShareCode ===

func TestResidenceService_GenerateShareCode_NotOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GenerateShareCode(residence.ID, shared.ID, 24)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.not_residence_owner")
}

func TestResidenceService_GenerateShareCode_DefaultExpiry(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Pass 0 hours — should default to 24
	resp, err := service.GenerateShareCode(residence.ID, user.ID, 0)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ShareCode.Code)
}

// === GenerateSharePackage ===

func TestResidenceService_GenerateSharePackage(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GenerateSharePackage(residence.ID, user.ID, 48)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ShareCode)
	assert.Equal(t, "Test House", resp.ResidenceName)
	assert.Equal(t, "owner@test.com", resp.SharedBy)
}

func TestResidenceService_GenerateSharePackage_NotOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GenerateSharePackage(residence.ID, shared.ID, 24)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.not_residence_owner")
}

// === JoinWithCode ===

func TestResidenceService_JoinWithCode_InvalidCode(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.JoinWithCode("BADCODE", user.ID)
	testutil.AssertAppError(t, err, http.StatusNotFound, "error.share_code_invalid")
}

// === RemoveUser ===

func TestResidenceService_RemoveUser_NotOwner(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// shared user tries to remove other — should fail because shared is not owner
	err := service.RemoveUser(residence.ID, other.ID, shared.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.not_residence_owner")
}

// === GetResidenceUsers ===

func TestResidenceService_GetResidenceUsers_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GetResidenceUsers(residence.ID, other.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

// === GetResidenceTypes ===

func TestResidenceService_GetResidenceTypes(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	resp, err := service.GetResidenceTypes()
	require.NoError(t, err)
	// SeedLookupData creates 4 residence types
	assert.Len(t, resp, 4)
}

// === UpdateResidence with home profile fields ===

func TestResidenceService_UpdateResidence_HomeProfileFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	hasPool := true
	hasGarage := true
	heatingType := "Forced Air"
	req := &requests.UpdateResidenceRequest{
		HasPool:     &hasPool,
		HasGarage:   &hasGarage,
		HeatingType: &heatingType,
	}

	resp, err := service.UpdateResidence(residence.ID, user.ID, req)
	require.NoError(t, err)
	assert.True(t, resp.Data.HasPool)
	assert.True(t, resp.Data.HasGarage)
}

// === CreateResidence with home profile fields ===

func TestResidenceService_CreateResidence_HomeProfileFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	hasPool := true
	hasSeptic := true
	req := &requests.CreateResidenceRequest{
		Name:          "New House",
		StreetAddress: "456 Oak St",
		City:          "Dallas",
		StateProvince: "TX",
		PostalCode:    "75201",
		HasPool:       &hasPool,
		HasSeptic:     &hasSeptic,
	}

	resp, err := service.CreateResidence(req, user.ID)
	require.NoError(t, err)
	assert.True(t, resp.Data.HasPool)
	assert.True(t, resp.Data.HasSeptic)
}

// === Shared user GetResidence ===

func TestResidenceService_GetResidence_SharedUser(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GetResidence(residence.ID, shared.ID, time.Now())
	require.NoError(t, err)
	assert.Equal(t, "Test House", resp.Name)
}

// === GetMyResidences with task repo (overdue counts + completion summaries) ===

func TestResidenceService_GetMyResidences_WithTaskRepo(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)
	service.SetTaskRepository(taskRepo)

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

	resp, err := service.GetMyResidences(user.ID, time.Now())
	require.NoError(t, err)
	assert.Len(t, resp.Residences, 2)
}

// === GetResidence with task repo (completion summary) ===

func TestResidenceService_GetResidence_WithTaskRepo(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)
	service.SetTaskRepository(taskRepo)

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

	resp, err := service.GetResidence(residence.ID, user.ID, time.Now())
	require.NoError(t, err)
	assert.Equal(t, "Test House", resp.Name)
}

// === GenerateShareCode with negative expiry defaults to 24 ===

func TestResidenceService_GenerateShareCode_NegativeExpiry(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.GenerateShareCode(residence.ID, user.ID, -5)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ShareCode.Code)
}

// === GenerateSharePackage with default expiry ===

func TestResidenceService_GenerateSharePackage_DefaultExpiry(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Pass 0 hours — should default to 24
	resp, err := service.GenerateSharePackage(residence.ID, user.ID, 0)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ShareCode)
	assert.Equal(t, "Test House", resp.ResidenceName)
}

// === RemoveUser — trying to remove the owner by a different owner ID ===

func TestResidenceService_RemoveUser_OwnerViaResidenceOwnerID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Try removing the owner (by residence.OwnerID) — even though requestingUserID != userIDToRemove
	// The second check (userIDToRemove == residence.OwnerID) should catch this
	err := service.RemoveUser(residence.ID, owner.ID, owner.ID)
	testutil.AssertAppError(t, err, http.StatusBadRequest, "error.cannot_remove_owner")
}

// === GenerateTasksReport ===

func TestResidenceService_GenerateTasksReport(t *testing.T) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Create some tasks
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2")

	report, err := service.GenerateTasksReport(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Equal(t, residence.ID, report.ResidenceID)
	assert.Equal(t, "Test House", report.ResidenceName)
	assert.Equal(t, 2, report.TotalTasks)
}

func TestResidenceService_GenerateTasksReport_AccessDenied(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	_, err := service.GenerateTasksReport(residence.ID, other.ID)
	testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}

func TestResidenceService_GenerateTasksReport_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Non-existent residence — user has no access
	_, err := service.GenerateTasksReport(9999, user.ID)
	assert.Error(t, err)
}

// === GetShareCode with active code ===

func TestResidenceService_GetShareCode_WithActiveCode(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	// Generate a share code first
	_, err := service.GenerateShareCode(residence.ID, user.ID, 24)
	require.NoError(t, err)

	// Now get the active code
	resp, err := service.GetShareCode(residence.ID, user.ID)
	require.NoError(t, err)
	assert.NotNil(t, resp)
	assert.NotEmpty(t, resp.Code)
}

// === CreateResidence with all boolean fields ===

func TestResidenceService_CreateResidence_AllBooleanFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	hasPool := true
	hasSprinkler := true
	hasSeptic := true
	hasFireplace := true
	hasGarage := true
	hasBasement := true
	hasAttic := true

	req := &requests.CreateResidenceRequest{
		Name:               "Full Feature House",
		StreetAddress:      "789 Full St",
		City:               "Austin",
		StateProvince:      "TX",
		PostalCode:         "78701",
		HasPool:            &hasPool,
		HasSprinklerSystem: &hasSprinkler,
		HasSeptic:          &hasSeptic,
		HasFireplace:       &hasFireplace,
		HasGarage:          &hasGarage,
		HasBasement:        &hasBasement,
		HasAttic:           &hasAttic,
	}

	resp, err := service.CreateResidence(req, user.ID)
	require.NoError(t, err)
	assert.True(t, resp.Data.HasPool)
	assert.True(t, resp.Data.HasSprinklerSystem)
	assert.True(t, resp.Data.HasSeptic)
	assert.True(t, resp.Data.HasFireplace)
	assert.True(t, resp.Data.HasGarage)
	assert.True(t, resp.Data.HasBasement)
	assert.True(t, resp.Data.HasAttic)
}

// === UpdateResidence with all optional fields ===

func TestResidenceService_UpdateResidence_AllOptionalFields(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	newStreet := "456 New St"
	newApt := "Apt 2B"
	newState := "CA"
	newPostal := "90210"
	newCountry := "Canada"
	bedrooms := 4
	bathrooms := decimal.NewFromFloat(3.0)
	sqft := 3000
	lotSize := decimal.NewFromFloat(0.5)
	yearBuilt := 2020
	newDesc := "Nice house"
	isPrimary := false
	hasPool := true
	hasSprinkler := true
	hasSeptic := false
	hasFireplace := true
	hasGarage := true
	hasBasement := false
	hasAttic := true
	coolingType := "Central AC"
	waterHeaterType := "Tankless"
	roofType := "Shingle"
	exteriorType := "Brick"
	flooringPrimary := "Hardwood"
	landscapingType := "Xeriscape"

	req := &requests.UpdateResidenceRequest{
		StreetAddress:      &newStreet,
		ApartmentUnit:      &newApt,
		StateProvince:      &newState,
		PostalCode:         &newPostal,
		Country:            &newCountry,
		Bedrooms:           &bedrooms,
		Bathrooms:          &bathrooms,
		SquareFootage:      &sqft,
		LotSize:            &lotSize,
		YearBuilt:          &yearBuilt,
		Description:        &newDesc,
		IsPrimary:          &isPrimary,
		HasPool:            &hasPool,
		HasSprinklerSystem: &hasSprinkler,
		HasSeptic:          &hasSeptic,
		HasFireplace:       &hasFireplace,
		HasGarage:          &hasGarage,
		HasBasement:        &hasBasement,
		HasAttic:           &hasAttic,
		CoolingType:        &coolingType,
		WaterHeaterType:    &waterHeaterType,
		RoofType:           &roofType,
		ExteriorType:       &exteriorType,
		FlooringPrimary:    &flooringPrimary,
		LandscapingType:    &landscapingType,
	}

	resp, err := service.UpdateResidence(residence.ID, user.ID, req)
	require.NoError(t, err)
	assert.Equal(t, "456 New St", resp.Data.StreetAddress)
	assert.Equal(t, "CA", resp.Data.StateProvince)
	assert.True(t, resp.Data.HasPool)
	assert.True(t, resp.Data.HasFireplace)
	assert.True(t, resp.Data.HasAttic)
}

// === ListResidences with no residences ===

func TestResidenceService_ListResidences_NoResidences(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

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

	resp, err := service.ListResidences(user.ID)
	require.NoError(t, err)
	assert.Empty(t, resp)
}

// === getSummaryForUser returns empty summary ===

func TestResidenceService_getSummaryForUser_ReturnsEmpty(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	service := NewResidenceService(residenceRepo, userRepo, cfg)

	summary := service.getSummaryForUser(999)
	assert.Equal(t, 0, summary.TotalResidences)
}
