package handlers

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

	"github.com/labstack/echo/v4"
	"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/services"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

// =============================================================================
// Suggestion Handler Tests (previously zero coverage)
// =============================================================================

func setupSuggestionHandler(t *testing.T) (*SuggestionHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	suggestionService := services.NewSuggestionService(db, residenceRepo)
	handler := NewSuggestionHandler(suggestionService)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestSuggestionHandler_GetSuggestions(t *testing.T) {
	handler, e, db := setupSuggestionHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/suggestions/", handler.GetSuggestions)

	t.Run("successful suggestions with valid residence", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/suggestions/?residence_id=%d", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("missing residence_id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/suggestions/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid residence_id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/suggestions/?residence_id=abc", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("access denied for other user residence", func(t *testing.T) {
		otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
		otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")

		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/suggestions/?residence_id=%d", otherResidence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestSuggestionHandler_NoAuth_Returns401(t *testing.T) {
	handler, _, _ := setupSuggestionHandler(t)
	e := testutil.SetupTestRouter()

	e.GET("/api/tasks/suggestions/", handler.GetSuggestions)

	w := testutil.MakeRequest(e, "GET", "/api/tasks/suggestions/?residence_id=1", nil, "")
	testutil.AssertStatusCode(t, w, http.StatusUnauthorized)
}

// =============================================================================
// Task Handler - Additional Error Path Tests
// =============================================================================

func TestTaskHandler_GetTask_InvalidID(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/", handler.GetTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestTaskHandler_UpdateTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.PUT("/:id/", handler.UpdateTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		newTitle := "Updated"
		req := requests.UpdateTaskRequest{Title: &newTitle}
		w := testutil.MakeRequest(e, "PUT", "/api/tasks/invalid/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		newTitle := "Updated"
		req := requests.UpdateTaskRequest{Title: &newTitle}
		w := testutil.MakeRequest(e, "PUT", "/api/tasks/99999/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})

	t.Run("access denied for other user task", func(t *testing.T) {
		otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
		otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
		otherTask := testutil.CreateTestTask(t, db, otherResidence.ID, otherUser.ID, "Other Task")

		newTitle := "Hacked"
		req := requests.UpdateTaskRequest{Title: &newTitle}
		w := testutil.MakeRequest(e, "PUT", fmt.Sprintf("/api/tasks/%d/", otherTask.ID), req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestTaskHandler_DeleteTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/:id/", handler.DeleteTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/tasks/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/tasks/99999/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_QuickComplete(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Quick Complete Me")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/quick-complete/", handler.QuickComplete)

	t.Run("successful quick complete", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/tasks/%d/quick-complete/", task.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/quick-complete/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/99999/quick-complete/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})

	t.Run("access denied for other user task", func(t *testing.T) {
		otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
		otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
		otherTask := testutil.CreateTestTask(t, db, otherResidence.ID, otherUser.ID, "Other Task")

		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/tasks/%d/quick-complete/", otherTask.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestTaskHandler_GetTaskCompletions(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Completions Task")

	// Create a completion
	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
		Notes:         "Done",
	}
	require.NoError(t, db.Create(completion).Error)

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/completions/", handler.GetTaskCompletions)

	t.Run("successful get completions", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/%d/completions/", task.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response []map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Len(t, response, 1)
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/invalid/completions/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/99999/completions/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_GetCompletion_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/task-completions")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/", handler.GetCompletion)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/task-completions/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/task-completions/99999/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_UpdateCompletion(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Update Completion Task")

	completion := &models.TaskCompletion{
		TaskID:        task.ID,
		CompletedByID: user.ID,
		CompletedAt:   time.Now().UTC(),
		Notes:         "Original notes",
	}
	require.NoError(t, db.Create(completion).Error)

	authGroup := e.Group("/api/task-completions")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.PUT("/:id/", handler.UpdateCompletion)

	t.Run("successful update", func(t *testing.T) {
		req := map[string]interface{}{
			"notes": "Updated notes",
		}
		w := testutil.MakeRequest(e, "PUT", fmt.Sprintf("/api/task-completions/%d/", completion.ID), req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, "Updated notes", response["notes"])
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		req := map[string]interface{}{"notes": "test"}
		w := testutil.MakeRequest(e, "PUT", "/api/task-completions/invalid/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		req := map[string]interface{}{"notes": "test"}
		w := testutil.MakeRequest(e, "PUT", "/api/task-completions/99999/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_DeleteCompletion_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/task-completions")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/:id/", handler.DeleteCompletion)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/task-completions/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/task-completions/99999/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_GetTasksByResidence_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/by-residence/:residence_id/", handler.GetTasksByResidence)

	t.Run("invalid residence id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/by-residence/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("access denied for other user residence", func(t *testing.T) {
		otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
		otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")

		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/by-residence/%d/", otherResidence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("days param out of range returns 400", func(t *testing.T) {
		residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/by-residence/%d/?days=5000", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestTaskHandler_ListTasks_DaysParam(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.ListTasks)

	t.Run("custom days param", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/?days=60", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, float64(60), response["days_threshold"])
	})

	t.Run("days_threshold param (backward compat)", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/?days_threshold=45", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, float64(45), response["days_threshold"])
	})

	t.Run("days out of range returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/?days=0", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("days too large returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/?days=4000", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestTaskHandler_CancelTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/cancel/", handler.CancelTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/cancel/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/99999/cancel/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_UncancelTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/uncancel/", handler.UncancelTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/uncancel/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not cancelled task returns error", func(t *testing.T) {
		residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
		task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Not Cancelled")

		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/tasks/%d/uncancel/", task.ID), nil, "test-token")
		// Service does not validate that the task is actually cancelled before uncancelling;
		// it succeeds silently (sets is_cancelled=false on an already non-cancelled task)
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestTaskHandler_ArchiveTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/archive/", handler.ArchiveTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/archive/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/99999/archive/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_UnarchiveTask_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/unarchive/", handler.UnarchiveTask)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/unarchive/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not archived task returns error", func(t *testing.T) {
		residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
		task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Not Archived")

		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/tasks/%d/unarchive/", task.ID), nil, "test-token")
		// Service does not validate that the task is actually archived before unarchiving;
		// it succeeds silently (sets is_archived=false on an already non-archived task)
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestTaskHandler_MarkInProgress_ErrorPaths(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/tasks")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/mark-in-progress/", handler.MarkInProgress)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/invalid/mark-in-progress/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/tasks/99999/mark-in-progress/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestTaskHandler_CreateCompletion_NoTaskID(t *testing.T) {
	handler, e, db := setupTaskHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/task-completions")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/", handler.CreateCompletion)

	t.Run("missing task_id returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"notes": "No task id",
		}
		w := testutil.MakeRequest(e, "POST", "/api/task-completions/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-existent task returns 404", func(t *testing.T) {
		completedAt := time.Now().UTC()
		req := requests.CreateTaskCompletionRequest{
			TaskID:      99999,
			CompletedAt: &completedAt,
		}
		w := testutil.MakeRequest(e, "POST", "/api/task-completions/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

// =============================================================================
// Auth Handler - Additional Coverage
// =============================================================================

func TestAuthHandler_AppleSignIn_NotConfigured(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/apple-sign-in/", handler.AppleSignIn)

	t.Run("returns 500 when apple auth not configured", func(t *testing.T) {
		req := map[string]interface{}{
			"id_token": "fake-token",
			"user_id":  "fake-user-id",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/apple-sign-in/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusInternalServerError)
	})

	t.Run("missing identity_token returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/apple-sign-in/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestAuthHandler_GoogleSignIn_NotConfigured(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/google-sign-in/", handler.GoogleSignIn)

	t.Run("returns 500 when google auth not configured", func(t *testing.T) {
		req := map[string]interface{}{
			"id_token": "fake-token",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/google-sign-in/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusInternalServerError)
	})

	t.Run("missing id_token returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/google-sign-in/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

// setupAuthHandlerWithDB is like setupAuthHandler but also returns the underlying *gorm.DB
// for tests that need to create records like ConfirmationCode directly.
func setupAuthHandlerWithDB(t *testing.T) (*AuthHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{
		Security: config.SecurityConfig{
			SecretKey:            "test-secret-key",
			PasswordResetExpiry:  15 * time.Minute,
			ConfirmationExpiry:   24 * time.Hour,
			MaxPasswordResetRate: 3,
		},
	}
	authService := services.NewAuthService(userRepo, cfg)
	handler := NewAuthHandler(authService, nil, nil)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestAuthHandler_VerifyEmail(t *testing.T) {
	handler, e, db := setupAuthHandlerWithDB(t)

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

	// Create confirmation code
	confirmCode := &models.ConfirmationCode{
		UserID:    user.ID,
		Code:      "123456",
		ExpiresAt: time.Now().Add(24 * time.Hour),
		IsUsed:    false,
	}
	require.NoError(t, db.Create(confirmCode).Error)

	authGroup := e.Group("/api/auth")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/verify-email/", handler.VerifyEmail)

	t.Run("successful verification", func(t *testing.T) {
		req := requests.VerifyEmailRequest{
			Code: "123456",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/verify-email/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, true, response["verified"])
	})

	t.Run("wrong code returns error", func(t *testing.T) {
		req := requests.VerifyEmailRequest{
			Code: "999999",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/verify-email/", req, "test-token")
		// Code already used or wrong code
		assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusNotFound,
			"expected 400 or 404, got %d", w.Code)
	})

	t.Run("missing code returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/verify-email/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestAuthHandler_ResendVerification(t *testing.T) {
	handler, e, db := setupAuthHandlerWithDB(t)

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

	authGroup := e.Group("/api/auth")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/resend-verification/", handler.ResendVerification)

	t.Run("successful resend", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/auth/resend-verification/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "message")
	})
}

func TestAuthHandler_RefreshToken(t *testing.T) {
	handler, e, db := setupAuthHandlerWithDB(t)

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

	// Create auth token and use its actual key in the middleware
	authToken := testutil.CreateTestToken(t, db, user.ID)

	authGroup := e.Group("/api/auth")
	authGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			c.Set("auth_user", user)
			c.Set("auth_token", authToken.Key)
			return next(c)
		}
	})
	authGroup.POST("/refresh/", handler.RefreshToken)

	t.Run("successful refresh", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/auth/refresh/", nil, authToken.Key)
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "token")
	})
}

func TestAuthHandler_VerifyResetCode(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/register/", handler.Register)
	e.POST("/api/auth/verify-reset-code/", handler.VerifyResetCode)

	t.Run("invalid code returns error", func(t *testing.T) {
		req := requests.VerifyResetCodeRequest{
			Email: "nonexistent@test.com",
			Code:  "999999",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/verify-reset-code/", req, "")
		// Should not be 200 since no valid code exists
		assert.NotEqual(t, http.StatusOK, w.Code)
	})

	t.Run("missing fields returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/verify-reset-code/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestAuthHandler_ResetPassword(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/reset-password/", handler.ResetPassword)

	t.Run("invalid reset token returns error", func(t *testing.T) {
		req := requests.ResetPasswordRequest{
			ResetToken:  "invalid-token",
			NewPassword: "NewPassword123",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/reset-password/", req, "")
		assert.NotEqual(t, http.StatusOK, w.Code)
	})

	t.Run("missing fields returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/reset-password/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("short password returns 400", func(t *testing.T) {
		req := requests.ResetPasswordRequest{
			ResetToken:  "some-token",
			NewPassword: "short",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/reset-password/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestAuthHandler_ForgotPassword_MissingEmail(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/forgot-password/", handler.ForgotPassword)

	t.Run("missing email returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/auth/forgot-password/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

// =============================================================================
// Residence Handler - Additional Error Paths
// =============================================================================

func TestResidenceHandler_GenerateShareCode_ErrorPaths(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	// Share with user
	residenceRepo := repositories.NewResidenceRepository(db)
	residenceRepo.AddUser(residence.ID, otherUser.ID)

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/generate-share-code/", handler.GenerateShareCode)

	otherGroup := e.Group("/api/other-residences")
	otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
	otherGroup.POST("/:id/generate-share-code/", handler.GenerateShareCode)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/invalid/generate-share-code/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-owner cannot generate share code", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/other-residences/%d/generate-share-code/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("non-existent residence returns error", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/99999/generate-share-code/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestResidenceHandler_GetShareCode_ErrorPaths(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/share-code/", handler.GetShareCode)

	otherGroup := e.Group("/api/other-residences")
	otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
	otherGroup.GET("/:id/share-code/", handler.GetShareCode)

	t.Run("non-member cannot get share code", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/other-residences/%d/share-code/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("non-existent residence returns error", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/residences/99999/share-code/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestResidenceHandler_JoinWithCode_ValidationErrors(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	newUser := testutil.CreateTestUser(t, db, "newuser", "new@test.com", "Password123")

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(newUser))
	authGroup.POST("/join-with-code/", handler.JoinWithCode)

	t.Run("empty code returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"code": "",
		}
		w := testutil.MakeRequest(e, "POST", "/api/residences/join-with-code/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("missing code returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/residences/join-with-code/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestResidenceHandler_GetResidenceUsers_ErrorPaths(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")

	otherGroup := e.Group("/api/other-residences")
	otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
	otherGroup.GET("/:id/users/", handler.GetResidenceUsers)

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/users/", handler.GetResidenceUsers)

	t.Run("access denied for non-member", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/other-residences/%d/users/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/residences/invalid/users/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestResidenceHandler_RemoveUser_ErrorPaths(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Remove Test")

	residenceRepo := repositories.NewResidenceRepository(db)
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/:id/users/:user_id/", handler.RemoveResidenceUser)

	sharedGroup := e.Group("/api/shared-residences")
	sharedGroup.Use(testutil.MockAuthMiddleware(sharedUser))
	sharedGroup.DELETE("/:id/users/:user_id/", handler.RemoveResidenceUser)

	t.Run("invalid residence id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/residences/invalid/users/%d/", sharedUser.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid user id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/residences/%d/users/invalid/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-owner cannot remove users", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/shared-residences/%d/users/%d/", residence.ID, user.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("remove non-existent user returns error", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/residences/%d/users/99999/", residence.ID), nil, "test-token")
		// Service does not verify that the user was actually a member before removing;
		// the repo delete affects 0 rows but does not return an error, so the handler returns 200
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestResidenceHandler_GenerateSharePackage_ErrorPaths(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/generate-share-package/", handler.GenerateSharePackage)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/invalid/generate-share-package/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-existent residence returns error", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/99999/generate-share-package/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestResidenceHandler_GenerateTasksReport(t *testing.T) {
	handler, e, db := setupResidenceHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Report Test")

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/generate-tasks-report/", handler.GenerateTasksReport)

	t.Run("successful report generation", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/residences/%d/generate-tasks-report/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "report")
		assert.Contains(t, response, "residence_name")
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/invalid/generate-tasks-report/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-existent residence returns error", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/residences/99999/generate-tasks-report/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestResidenceHandler_GenerateTasksReport_Disabled(t *testing.T) {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{}
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	// Create handler with PDF reports DISABLED
	handler := NewResidenceHandler(residenceService, nil, nil, false)
	e := testutil.SetupTestRouter()

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

	authGroup := e.Group("/api/residences")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/generate-tasks-report/", handler.GenerateTasksReport)

	t.Run("feature disabled returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/residences/%d/generate-tasks-report/", residence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

// =============================================================================
// Contractor Handler - Additional Error Paths
// =============================================================================

func TestContractorHandler_ListContractorsByResidence_AccessDenied(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/by-residence/:residence_id/", handler.ListContractorsByResidence)

	t.Run("access denied for non-member residence", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/contractors/by-residence/%d/", otherResidence.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestContractorHandler_GetContractor_AccessDenied(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
	otherContractor := testutil.CreateTestContractor(t, db, otherResidence.ID, otherUser.ID, "Other Plumber")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/", handler.GetContractor)

	t.Run("access denied for other user contractor", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/contractors/%d/", otherContractor.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestContractorHandler_UpdateContractor_AccessDenied(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
	otherContractor := testutil.CreateTestContractor(t, db, otherResidence.ID, otherUser.ID, "Other Plumber")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.PUT("/:id/", handler.UpdateContractor)

	t.Run("access denied for other user contractor", func(t *testing.T) {
		newName := "Hacked"
		req := requests.UpdateContractorRequest{Name: &newName}
		w := testutil.MakeRequest(e, "PUT", fmt.Sprintf("/api/contractors/%d/", otherContractor.ID), req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestContractorHandler_DeleteContractor_AccessDenied(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
	otherContractor := testutil.CreateTestContractor(t, db, otherResidence.ID, otherUser.ID, "Other Plumber")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/:id/", handler.DeleteContractor)

	t.Run("access denied for other user contractor", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/contractors/%d/", otherContractor.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestContractorHandler_ToggleFavorite_AccessDenied(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
	otherContractor := testutil.CreateTestContractor(t, db, otherResidence.ID, otherUser.ID, "Other Plumber")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/toggle-favorite/", handler.ToggleFavorite)

	t.Run("access denied for other user contractor", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/contractors/%d/toggle-favorite/", otherContractor.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

func TestContractorHandler_GetContractorTasks_NotFound(t *testing.T) {
	handler, e, db := setupContractorHandler(t)
	testutil.SeedLookupData(t, db)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/contractors")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/tasks/", handler.GetContractorTasks)

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/contractors/99999/tasks/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

// =============================================================================
// Document Handler - Additional Error Paths
// =============================================================================

func TestDocumentHandler_UploadDocumentImage_InvalidIDs(t *testing.T) {
	handler, e, db := setupDocumentHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/documents")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/images/", handler.UploadDocumentImage)

	t.Run("invalid document id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/documents/invalid/images/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestDocumentHandler_DeleteDocumentImage_InvalidIDs(t *testing.T) {
	handler, e, db := setupDocumentHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/documents")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/:id/images/:imageId/", handler.DeleteDocumentImage)

	t.Run("invalid document id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/documents/invalid/images/1/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid image id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/documents/1/images/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestDocumentHandler_ListDocuments_TypeFilter(t *testing.T) {
	handler, e, db := setupDocumentHandler(t)
	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, "Warranty Doc")
	require.NoError(t, db.Model(doc).Update("document_type", "warranty").Error)

	authGroup := e.Group("/api/documents")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.ListDocuments)

	t.Run("filter by document_type", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/documents/?document_type=warranty", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response []map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Len(t, response, 1)
	})

	t.Run("filter by non-matching type returns empty", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/documents/?document_type=insurance", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response []map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Len(t, response, 0)
	})

	t.Run("filter by expiring_soon valid", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/documents/?expiring_soon=30", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("negative expiring_soon out of range", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/documents/?expiring_soon=-1", nil, "test-token")
		// -1 parses successfully via Atoi, then fails the <1 range check, returning 400
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestDocumentHandler_ActivateDeactivate_AccessDenied(t *testing.T) {
	handler, e, db := setupDocumentHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
	doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")

	otherGroup := e.Group("/api/documents")
	otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
	otherGroup.POST("/:id/activate/", handler.ActivateDocument)
	otherGroup.POST("/:id/deactivate/", handler.DeactivateDocument)

	t.Run("activate access denied", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/documents/%d/activate/", doc.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})

	t.Run("deactivate access denied", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/documents/%d/deactivate/", doc.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

// =============================================================================
// Notification Handler - Additional Coverage
// =============================================================================

func TestNotificationHandler_RegisterDevice_Android(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/devices/", handler.RegisterDevice)

	t.Run("successful android device registration", func(t *testing.T) {
		req := map[string]interface{}{
			"name":            "Pixel 8",
			"device_id":       "android-device-123",
			"registration_id": "android-reg-abc",
			"platform":        "android",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusCreated)
	})

	t.Run("duplicate registration updates existing", func(t *testing.T) {
		req := map[string]interface{}{
			"name":            "Pixel 8 Updated",
			"device_id":       "android-device-123",
			"registration_id": "android-reg-abc",
			"platform":        "android",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
		// Should succeed (upsert behavior)
		assert.True(t, w.Code == http.StatusCreated || w.Code == http.StatusOK,
			"expected 201 or 200, got %d", w.Code)
	})
}

func TestNotificationHandler_UnregisterDevice_Valid(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/devices/", handler.RegisterDevice)
	authGroup.POST("/devices/unregister/", handler.UnregisterDevice)

	// First register a device
	regReq := map[string]interface{}{
		"name":            "iPhone 15",
		"device_id":       "test-device-unreg",
		"registration_id": "test-reg-unreg",
		"platform":        "ios",
	}
	testutil.MakeRequest(e, "POST", "/api/notifications/devices/", regReq, "test-token")

	t.Run("successful unregister", func(t *testing.T) {
		req := map[string]interface{}{
			"registration_id": "test-reg-unreg",
			"platform":        "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("unregister non-existent device", func(t *testing.T) {
		req := map[string]interface{}{
			"registration_id": "nonexistent-reg-id",
			"platform":        "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
		// Should be 404 or succeed silently
		assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound,
			"expected 200 or 404, got %d", w.Code)
	})
}

func TestNotificationHandler_DeleteDevice_Valid(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	// Create a device directly
	apnsDevice := &models.APNSDevice{
		UserID:         &user.ID,
		Name:           "Test iPhone",
		DeviceID:       "delete-device-id",
		RegistrationID: "delete-reg-id",
		Active:         true,
	}
	require.NoError(t, db.Create(apnsDevice).Error)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/devices/:id/", handler.DeleteDevice)

	t.Run("successful delete with platform", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/notifications/devices/%d/?platform=ios", apnsDevice.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("not found after delete", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/notifications/devices/%d/?platform=ios", apnsDevice.ID), nil, "test-token")
		// Device is deactivated (active=false) rather than deleted, so the second call
		// still finds the device and "deactivates" it again, returning 200
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

// =============================================================================
// User Handler Tests (previously zero functional tests)
// =============================================================================

func setupUserHandler(t *testing.T) (*UserHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	userRepo := repositories.NewUserRepository(db)
	userService := services.NewUserService(userRepo)
	handler := NewUserHandler(userService)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestUserHandler_ListUsers(t *testing.T) {
	handler, e, db := setupUserHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")

	// Create residence and share it
	residence := testutil.CreateTestResidence(t, db, user.ID, "Shared House")
	residenceRepo := repositories.NewResidenceRepository(db)
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	authGroup := e.Group("/api/users")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.ListUsers)

	t.Run("list users in shared residences", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/users/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "count")
		assert.Contains(t, response, "results")
	})
}

func TestUserHandler_GetUser(t *testing.T) {
	handler, e, db := setupUserHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
	outsideUser := testutil.CreateTestUser(t, db, "outside", "outside@test.com", "Password123")

	// Create residence and share it
	residence := testutil.CreateTestResidence(t, db, user.ID, "Shared House")
	residenceRepo := repositories.NewResidenceRepository(db)
	residenceRepo.AddUser(residence.ID, sharedUser.ID)

	authGroup := e.Group("/api/users")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/:id/", handler.GetUser)

	t.Run("get shared user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/users/%d/", sharedUser.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("access denied for non-shared user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/users/%d/", outsideUser.ID), nil, "test-token")
		// Service returns "user not found" (404) rather than "forbidden" (403) for
		// non-shared users — this avoids revealing that a user exists
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/users/invalid/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/users/99999/", nil, "test-token")
		assert.True(t, w.Code == http.StatusNotFound || w.Code == http.StatusForbidden,
			"expected 404 or 403, got %d", w.Code)
	})
}

func TestUserHandler_ListProfiles(t *testing.T) {
	handler, e, db := setupUserHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/users")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/profiles/", handler.ListProfiles)

	t.Run("successful list profiles", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/users/profiles/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "count")
		assert.Contains(t, response, "results")
	})
}

// =============================================================================
// Subscription Handler Tests (previously zero functional tests)
// =============================================================================

func setupSubscriptionHandler(t *testing.T) (*SubscriptionHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	subscriptionRepo := repositories.NewSubscriptionRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)
	documentRepo := repositories.NewDocumentRepository(db)
	subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo)
	handler := NewSubscriptionHandler(subscriptionService, nil)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestSubscriptionHandler_GetSubscription(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.GetSubscription)

	t.Run("get subscription for new user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestSubscriptionHandler_GetSubscriptionStatus(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/status/", handler.GetSubscriptionStatus)

	t.Run("get subscription status", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/status/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "tier")
	})
}

func TestSubscriptionHandler_ProcessPurchase_ValidationErrors(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/purchase/", handler.ProcessPurchase)

	t.Run("missing fields returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/purchase/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid platform returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"platform": "windows",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/purchase/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("ios without receipt returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"platform": "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/purchase/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("android without purchase token returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"platform": "android",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/purchase/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestSubscriptionHandler_RestoreSubscription_ValidationErrors(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/restore/", handler.RestoreSubscription)

	t.Run("missing fields returns 400", func(t *testing.T) {
		req := map[string]interface{}{}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/restore/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("ios without receipt returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"platform": "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/restore/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestSubscriptionHandler_GetPromotions(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/promotions/", handler.GetPromotions)

	t.Run("get promotions for user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/promotions/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestSubscriptionHandler_GetFeatureBenefits(t *testing.T) {
	handler, e, _ := setupSubscriptionHandler(t)

	e.GET("/api/subscription/features/", handler.GetFeatureBenefits)

	t.Run("get feature benefits", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/features/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestSubscriptionHandler_GetAllUpgradeTriggers(t *testing.T) {
	handler, e, _ := setupSubscriptionHandler(t)

	e.GET("/api/subscription/upgrade-triggers/", handler.GetAllUpgradeTriggers)

	t.Run("get all upgrade triggers", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/upgrade-triggers/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestSubscriptionHandler_GetUpgradeTrigger(t *testing.T) {
	handler, e, _ := setupSubscriptionHandler(t)

	e.GET("/api/subscription/upgrade-trigger/:key/", handler.GetUpgradeTrigger)

	t.Run("non-existent trigger key returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/subscription/upgrade-trigger/nonexistent/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestSubscriptionHandler_CancelSubscription(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/cancel/", handler.CancelSubscription)

	t.Run("cancel subscription for free user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/subscription/cancel/", nil, "test-token")
		// Free user cancelling might return 400 or success
		assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusBadRequest,
			"expected 200 or 400, got %d", w.Code)
	})
}

func TestSubscriptionHandler_CreateCheckoutSession_NotConfigured(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/checkout/", handler.CreateCheckoutSession)

	t.Run("stripe not configured returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"price_id":    "price_123",
			"success_url": "https://example.com/success",
			"cancel_url":  "https://example.com/cancel",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/checkout/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestSubscriptionHandler_CreatePortalSession_NotConfigured(t *testing.T) {
	handler, e, db := setupSubscriptionHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/subscription")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/portal/", handler.CreatePortalSession)

	t.Run("stripe not configured returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"return_url": "https://example.com/return",
		}
		w := testutil.MakeRequest(e, "POST", "/api/subscription/portal/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

// =============================================================================
// Task Template Handler Tests (previously zero coverage)
// =============================================================================

func setupTaskTemplateHandler(t *testing.T) (*TaskTemplateHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	templateRepo := repositories.NewTaskTemplateRepository(db)
	templateService := services.NewTaskTemplateService(templateRepo)
	handler := NewTaskTemplateHandler(templateService)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestTaskTemplateHandler_GetTemplates(t *testing.T) {
	handler, e, db := setupTaskTemplateHandler(t)
	testutil.SeedLookupData(t, db)

	e.GET("/api/tasks/templates/", handler.GetTemplates)

	t.Run("get all templates", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestTaskTemplateHandler_GetTemplatesGrouped(t *testing.T) {
	handler, e, db := setupTaskTemplateHandler(t)
	testutil.SeedLookupData(t, db)

	e.GET("/api/tasks/templates/grouped/", handler.GetTemplatesGrouped)

	t.Run("get grouped templates", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/grouped/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestTaskTemplateHandler_SearchTemplates(t *testing.T) {
	handler, e, db := setupTaskTemplateHandler(t)
	testutil.SeedLookupData(t, db)

	e.GET("/api/tasks/templates/search/", handler.SearchTemplates)

	t.Run("missing query returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/search/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("query too short returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/search/?q=a", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("valid query returns 200", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/search/?q=plumbing", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestTaskTemplateHandler_GetTemplatesByCategory(t *testing.T) {
	handler, e, db := setupTaskTemplateHandler(t)
	testutil.SeedLookupData(t, db)

	e.GET("/api/tasks/templates/by-category/:category_id/", handler.GetTemplatesByCategory)

	t.Run("valid category id", func(t *testing.T) {
		var cat models.TaskCategory
		db.First(&cat)
		w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/tasks/templates/by-category/%d/", cat.ID), nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("invalid category id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/by-category/invalid/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

// NOTE: TestTaskTemplateHandler_GetTemplatesByRegion was removed. The
// /api/tasks/templates/by-region/ endpoint was deleted; climate-zone
// affinity is now a JSON condition on each template and is scored by the
// main /api/tasks/suggestions/ endpoint (see SuggestionService tests).

func TestTaskTemplateHandler_GetTemplate(t *testing.T) {
	handler, e, db := setupTaskTemplateHandler(t)
	testutil.SeedLookupData(t, db)

	e.GET("/api/tasks/templates/:id/", handler.GetTemplate)

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/invalid/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("non-existent id returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/tasks/templates/99999/", nil, "")
		// Service does not wrap gorm.ErrRecordNotFound as a NotFound error,
		// so the raw error falls through to the global error handler as 500
		testutil.AssertStatusCode(t, w, http.StatusInternalServerError)
	})
}

// =============================================================================
// Tracking Handler Tests (previously zero coverage)
// =============================================================================

func TestTrackingHandler_TrackEmailOpen(t *testing.T) {
	handler := NewTrackingHandler(nil) // nil service -- won't record, but returns pixel
	e := testutil.SetupTestRouter()

	e.GET("/api/track/open/:trackingID", handler.TrackEmailOpen)

	t.Run("returns transparent GIF", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/track/open/test-tracking-id", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
		assert.Equal(t, "image/gif", w.Header().Get("Content-Type"))
		assert.Equal(t, "no-store, no-cache, must-revalidate, proxy-revalidate", w.Header().Get("Cache-Control"))
		assert.Greater(t, w.Body.Len(), 0)
	})

	t.Run("empty tracking id still returns GIF", func(t *testing.T) {
		e2 := testutil.SetupTestRouter()
		e2.GET("/api/track/open/", handler.TrackEmailOpen)

		w := testutil.MakeRequest(e2, "GET", "/api/track/open/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

// =============================================================================
// Static Data Handler Tests (previously zero coverage)
// =============================================================================

func setupStaticDataHandler(t *testing.T) (*StaticDataHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)

	residenceRepo := repositories.NewResidenceRepository(db)
	userRepo := repositories.NewUserRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)
	templateRepo := repositories.NewTaskTemplateRepository(db)

	cfg := &config.Config{}
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	taskService := services.NewTaskService(taskRepo, residenceRepo)
	contractorService := services.NewContractorService(contractorRepo, residenceRepo)
	templateService := services.NewTaskTemplateService(templateRepo)

	handler := NewStaticDataHandler(residenceService, taskService, contractorService, templateService, nil)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func TestStaticDataHandler_GetStaticData(t *testing.T) {
	handler, e, _ := setupStaticDataHandler(t)

	e.GET("/api/static_data/", handler.GetStaticData)

	t.Run("returns all lookup data", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/static_data/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Contains(t, response, "residence_types")
		assert.Contains(t, response, "task_categories")
		assert.Contains(t, response, "task_priorities")
		assert.Contains(t, response, "task_frequencies")
		assert.Contains(t, response, "contractor_specialties")
		assert.Contains(t, response, "task_templates")
	})
}

func TestStaticDataHandler_RefreshStaticData(t *testing.T) {
	handler, e, _ := setupStaticDataHandler(t)

	e.POST("/api/static_data/refresh/", handler.RefreshStaticData)

	t.Run("returns success", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/static_data/refresh/", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, "success", response["status"])
	})
}

// =============================================================================
// Upload Handler - Additional Error Paths
// =============================================================================

func TestUploadHandler_UploadImage_NoFile(t *testing.T) {
	storageSvc := newTestStorageService("/var/uploads")
	handler := NewUploadHandler(storageSvc, nil)
	e := testutil.SetupTestRouter()

	e.POST("/api/uploads/image", handler.UploadImage)

	t.Run("no file returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/uploads/image", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestUploadHandler_UploadDocument_NoFile(t *testing.T) {
	storageSvc := newTestStorageService("/var/uploads")
	handler := NewUploadHandler(storageSvc, nil)
	e := testutil.SetupTestRouter()

	e.POST("/api/uploads/document", handler.UploadDocument)

	t.Run("no file returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/uploads/document", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestUploadHandler_UploadCompletion_NoFile(t *testing.T) {
	storageSvc := newTestStorageService("/var/uploads")
	handler := NewUploadHandler(storageSvc, nil)
	e := testutil.SetupTestRouter()

	e.POST("/api/uploads/completion", handler.UploadCompletion)

	t.Run("no file returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/uploads/completion", nil, "")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestUploadHandler_DeleteFile_OwnershipDenied(t *testing.T) {
	storageSvc := newTestStorageService("/var/uploads")

	// Mock ownership checker that always denies
	checker := &mockOwnershipChecker{owned: false}
	handler := NewUploadHandler(storageSvc, checker)
	e := testutil.SetupTestRouter()

	testUser := &models.User{FirstName: "Test", Email: "test@test.com"}
	testUser.ID = 1
	authGroup := e.Group("/api")
	authGroup.Use(testutil.MockAuthMiddleware(testUser))
	authGroup.DELETE("/uploads/", handler.DeleteFile)

	t.Run("ownership denied returns 403", func(t *testing.T) {
		req := map[string]string{"url": "/uploads/images/test.jpg"}
		w := testutil.MakeRequest(e, "DELETE", "/api/uploads/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusForbidden)
	})
}

// mockOwnershipChecker implements FileOwnershipChecker for testing
type mockOwnershipChecker struct {
	owned bool
}

func (m *mockOwnershipChecker) IsFileOwnedByUser(fileURL string, userID uint) (bool, error) {
	return m.owned, nil
}
