package integration

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

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/treytartt/honeydue-api/internal/apperrors"
	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/handlers"
	"github.com/treytartt/honeydue-api/internal/middleware"
	"github.com/treytartt/honeydue-api/internal/repositories"
	"github.com/treytartt/honeydue-api/internal/services"
	"github.com/treytartt/honeydue-api/internal/testutil"
	"github.com/treytartt/honeydue-api/internal/validator"
	"gorm.io/gorm"
)

// ============ Response Structs for Type Safety ============
// These avoid map[string]interface{} casts and provide better error messages

// AuthResponse represents login/register response
type AuthResponse struct {
	Token string       `json:"token"`
	User  UserResponse `json:"user"`
}

// UserResponse represents user data in responses
type UserResponse struct {
	ID       uint   `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
}

// DataWrapper wraps responses with a "data" field
type DataWrapper[T any] struct {
	Data T `json:"data"`
}

// TaskResponse represents task data in responses
type TaskResponse struct {
	ID           uint    `json:"id"`
	Title        string  `json:"title"`
	Description  string  `json:"description"`
	ResidenceID  uint    `json:"residence_id"`
	KanbanColumn string  `json:"kanban_column"`
	InProgress   bool    `json:"in_progress"`
	IsCancelled  bool    `json:"is_cancelled"`
	IsArchived   bool    `json:"is_archived"`
	DueDate      *string `json:"due_date"`
	NextDueDate  *string `json:"next_due_date"`
}

// KanbanResponse represents the kanban board response
type KanbanResponse struct {
	Columns       []KanbanColumnResponse `json:"columns"`
	DaysThreshold int                    `json:"days_threshold"`
	ResidenceID   string                 `json:"residence_id"`
}

// KanbanColumnResponse represents a single kanban column
type KanbanColumnResponse struct {
	Name        string         `json:"name"`
	DisplayName string         `json:"display_name"`
	Count       int            `json:"count"`
	Tasks       []TaskResponse `json:"tasks"`
}

// ResidenceResponse represents residence data
type ResidenceResponse struct {
	ID        uint   `json:"id"`
	Name      string `json:"name"`
	IsPrimary bool   `json:"is_primary"`
}

// ShareCodeResponse represents share code generation response
type ShareCodeResponse struct {
	ShareCode ShareCodeData `json:"share_code"`
}

// ShareCodeData represents the share code object
type ShareCodeData struct {
	Code      string `json:"code"`
	ExpiresAt string `json:"expires_at"`
}

// TestApp holds all components for integration testing
type TestApp struct {
	DB                *gorm.DB
	Router            *echo.Echo
	AuthHandler       *handlers.AuthHandler
	ResidenceHandler  *handlers.ResidenceHandler
	TaskHandler       *handlers.TaskHandler
	ContractorHandler *handlers.ContractorHandler
	UserRepo          *repositories.UserRepository
	ResidenceRepo     *repositories.ResidenceRepository
	TaskRepo          *repositories.TaskRepository
	ContractorRepo    *repositories.ContractorRepository
	AuthService       *services.AuthService
}

func setupIntegrationTest(t *testing.T) *TestApp {
	// Echo does not need test mode

	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)

	// Create repositories
	userRepo := repositories.NewUserRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)

	// Create config
	cfg := &config.Config{
		Security: config.SecurityConfig{
			SecretKey:            "test-secret-key-for-integration-tests",
			PasswordResetExpiry:  15 * time.Minute,
			ConfirmationExpiry:   24 * time.Hour,
			MaxPasswordResetRate: 3,
		},
	}

	// Create services
	authService := services.NewAuthService(userRepo, cfg)
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	taskService := services.NewTaskService(taskRepo, residenceRepo)
	contractorService := services.NewContractorService(contractorRepo, residenceRepo)

	// Create handlers
	authHandler := handlers.NewAuthHandler(authService, nil, nil)
	residenceHandler := handlers.NewResidenceHandler(residenceService, nil, nil, true)
	taskHandler := handlers.NewTaskHandler(taskService, nil)
	contractorHandler := handlers.NewContractorHandler(contractorService)

	// Create router with real middleware
	e := echo.New()
	e.Validator = validator.NewCustomValidator()
	e.HTTPErrorHandler = apperrors.HTTPErrorHandler

	// Add timezone middleware globally so X-Timezone header is processed
	e.Use(middleware.TimezoneMiddleware())

	// Public routes
	auth := e.Group("/api/auth")
	{
		auth.POST("/register", authHandler.Register)
		auth.POST("/login", authHandler.Login)
	}

	// Protected routes - use AuthMiddleware without Redis cache for testing
	authMiddleware := middleware.NewAuthMiddleware(db, nil)
	api := e.Group("/api")
	api.Use(authMiddleware.TokenAuth())
	{
		api.GET("/auth/me", authHandler.CurrentUser)
		api.POST("/auth/logout", authHandler.Logout)

		residences := api.Group("/residences")
		{
			residences.GET("", residenceHandler.ListResidences)
			residences.POST("", residenceHandler.CreateResidence)
			residences.GET("/:id", residenceHandler.GetResidence)
			residences.PUT("/:id", residenceHandler.UpdateResidence)
			residences.DELETE("/:id", residenceHandler.DeleteResidence)
			residences.POST("/:id/generate-share-code", residenceHandler.GenerateShareCode)
			residences.GET("/:id/users", residenceHandler.GetResidenceUsers)
			residences.DELETE("/:id/users/:user_id", residenceHandler.RemoveResidenceUser)
		}
		api.POST("/residences/join-with-code", residenceHandler.JoinWithCode)
		api.GET("/residence-types", residenceHandler.GetResidenceTypes)

		tasks := api.Group("/tasks")
		{
			tasks.GET("", taskHandler.ListTasks)
			tasks.POST("", taskHandler.CreateTask)
			tasks.GET("/:id", taskHandler.GetTask)
			tasks.PUT("/:id", taskHandler.UpdateTask)
			tasks.DELETE("/:id", taskHandler.DeleteTask)
			tasks.POST("/:id/cancel", taskHandler.CancelTask)
			tasks.POST("/:id/uncancel", taskHandler.UncancelTask)
			tasks.POST("/:id/archive", taskHandler.ArchiveTask)
			tasks.POST("/:id/unarchive", taskHandler.UnarchiveTask)
			tasks.POST("/:id/mark-in-progress", taskHandler.MarkInProgress)
		}
		api.GET("/tasks/by-residence/:residence_id", taskHandler.GetTasksByResidence)

		completions := api.Group("/completions")
		{
			completions.GET("", taskHandler.ListCompletions)
			completions.POST("", taskHandler.CreateCompletion)
			completions.GET("/:id", taskHandler.GetCompletion)
			completions.DELETE("/:id", taskHandler.DeleteCompletion)
		}

		api.GET("/task-categories", taskHandler.GetCategories)
		api.GET("/task-priorities", taskHandler.GetPriorities)
		api.GET("/task-frequencies", taskHandler.GetFrequencies)

		contractors := api.Group("/contractors")
		{
			contractors.GET("", contractorHandler.ListContractors)
			contractors.POST("", contractorHandler.CreateContractor)
			contractors.GET("/:id", contractorHandler.GetContractor)
			contractors.PUT("/:id", contractorHandler.UpdateContractor)
			contractors.DELETE("/:id", contractorHandler.DeleteContractor)
		}
		api.GET("/contractors/by-residence/:residence_id", contractorHandler.ListContractorsByResidence)
	}

	return &TestApp{
		DB:                db,
		Router:            e,
		AuthHandler:       authHandler,
		ResidenceHandler:  residenceHandler,
		TaskHandler:       taskHandler,
		ContractorHandler: contractorHandler,
		UserRepo:          userRepo,
		ResidenceRepo:     residenceRepo,
		TaskRepo:          taskRepo,
		ContractorRepo:    contractorRepo,
		AuthService:       authService,
	}
}

// Helper to make authenticated requests
func (app *TestApp) makeAuthenticatedRequest(t *testing.T, method, path string, body interface{}, token string) *httptest.ResponseRecorder {
	var reqBody []byte
	var err error
	if body != nil {
		reqBody, err = json.Marshal(body)
		require.NoError(t, err)
	}

	req := httptest.NewRequest(method, path, bytes.NewBuffer(reqBody))
	req.Header.Set("Content-Type", "application/json")
	if token != "" {
		req.Header.Set("Authorization", "Token "+token)
	}

	w := httptest.NewRecorder()
	app.Router.ServeHTTP(w, req)
	return w
}

// Helper to register and login a user, returns token
func (app *TestApp) registerAndLogin(t *testing.T, username, email, password string) string {
	// Register
	registerBody := map[string]string{
		"username": username,
		"email":    email,
		"password": password,
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody, "")
	require.Equal(t, http.StatusCreated, w.Code)

	// Login
	loginBody := map[string]string{
		"username": username,
		"password": password,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/login", loginBody, "")
	require.Equal(t, http.StatusOK, w.Code)

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

	return loginResp["token"].(string)
}

// ============ Authentication Flow Tests ============

func TestIntegration_AuthenticationFlow(t *testing.T) {
	app := setupIntegrationTest(t)

	// 1. Register a new user
	registerBody := map[string]string{
		"username": "testuser",
		"email":    "test@example.com",
		"password": "SecurePass123!",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody, "")
	assert.Equal(t, http.StatusCreated, w.Code)

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

	// 2. Login with the same credentials
	loginBody := map[string]string{
		"username": "testuser",
		"password": "SecurePass123!",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/login", loginBody, "")
	assert.Equal(t, http.StatusOK, w.Code)

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

	// 3. Get current user with token
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var meResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &meResp)
	require.NoError(t, err)
	assert.Equal(t, "testuser", meResp["username"])
	assert.Equal(t, "test@example.com", meResp["email"])

	// 4. Access protected route without token should fail
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, "")
	assert.Equal(t, http.StatusUnauthorized, w.Code)

	// 5. Access protected route with invalid token should fail
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, "invalid-token")
	assert.Equal(t, http.StatusUnauthorized, w.Code)

	// 6. Logout
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/logout", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)
}

func TestIntegration_RegistrationValidation(t *testing.T) {
	app := setupIntegrationTest(t)

	tests := []struct {
		name           string
		body           map[string]string
		expectedStatus int
	}{
		{
			name:           "missing username",
			body:           map[string]string{"email": "test@example.com", "password": "pass123"},
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "missing email",
			body:           map[string]string{"username": "testuser", "password": "pass123"},
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "missing password",
			body:           map[string]string{"username": "testuser", "email": "test@example.com"},
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "invalid email",
			body:           map[string]string{"username": "testuser", "email": "invalid", "password": "pass123"},
			expectedStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", tt.body, "")
			assert.Equal(t, tt.expectedStatus, w.Code)
		})
	}
}

func TestIntegration_DuplicateRegistration(t *testing.T) {
	app := setupIntegrationTest(t)

	// Register first user (password must be >= 8 chars)
	registerBody := map[string]string{
		"username": "testuser",
		"email":    "test@example.com",
		"password": "Password123",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody, "")
	assert.Equal(t, http.StatusCreated, w.Code)

	// Try to register with same username - returns 409 (Conflict)
	registerBody2 := map[string]string{
		"username": "testuser",
		"email":    "different@example.com",
		"password": "Password123",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody2, "")
	assert.Equal(t, http.StatusConflict, w.Code)

	// Try to register with same email - returns 409 (Conflict)
	registerBody3 := map[string]string{
		"username": "differentuser",
		"email":    "test@example.com",
		"password": "Password123",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody3, "")
	assert.Equal(t, http.StatusConflict, w.Code)
}

// ============ Residence Flow Tests ============

func TestIntegration_ResidenceFlow(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")

	// 1. Create a residence
	createBody := map[string]interface{}{
		"name":           "My House",
		"street_address": "123 Main St",
		"city":           "Austin",
		"state_province": "TX",
		"postal_code":    "78701",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", createBody, token)
	assert.Equal(t, http.StatusCreated, w.Code)

	var createResp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &createResp)
	require.NoError(t, err)
	createData := createResp["data"].(map[string]interface{})
	residenceID := createData["id"].(float64)
	assert.NotZero(t, residenceID)
	assert.Equal(t, "My House", createData["name"])
	assert.True(t, createData["is_primary"].(bool))

	// 2. Get the residence
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var getResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &getResp)
	require.NoError(t, err)
	assert.Equal(t, "My House", getResp["name"])

	// 3. List residences
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

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

	// 4. Update the residence
	updateBody := map[string]interface{}{
		"name": "My Updated House",
		"city": "Dallas",
	}
	w = app.makeAuthenticatedRequest(t, "PUT", "/api/residences/"+formatID(residenceID), updateBody, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var updateResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &updateResp)
	require.NoError(t, err)
	updateData := updateResp["data"].(map[string]interface{})
	assert.Equal(t, "My Updated House", updateData["name"])
	assert.Equal(t, "Dallas", updateData["city"])

	// 5. Delete the residence (returns 200 with message, not 204)
	w = app.makeAuthenticatedRequest(t, "DELETE", "/api/residences/"+formatID(residenceID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	// 6. Verify it's deleted (should return 403 - access denied since it doesn't exist/inactive)
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, token)
	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestIntegration_ResidenceSharingFlow(t *testing.T) {
	app := setupIntegrationTest(t)

	// Create owner and another user
	ownerToken := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")
	userToken := app.registerAndLogin(t, "shareduser", "shared@test.com", "Password123")

	// Create residence as owner
	createBody := map[string]interface{}{
		"name": "Shared House",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", createBody, ownerToken)
	require.Equal(t, http.StatusCreated, w.Code)

	var createResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &createResp)
	createData := createResp["data"].(map[string]interface{})
	residenceID := createData["id"].(float64)

	// Other user cannot access initially
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, userToken)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// Generate share code
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/"+formatID(residenceID)+"/generate-share-code", nil, ownerToken)
	assert.Equal(t, http.StatusOK, w.Code)

	var shareResp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &shareResp)
	require.NoError(t, err)
	shareCodeObj, ok := shareResp["share_code"].(map[string]interface{})
	require.True(t, ok, "Expected share_code object in response")
	shareCode := shareCodeObj["code"].(string)
	assert.Len(t, shareCode, 6)

	// User joins with code
	joinBody := map[string]interface{}{
		"code": shareCode,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", joinBody, userToken)
	assert.Equal(t, http.StatusOK, w.Code)

	// Now user can access
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, userToken)
	assert.Equal(t, http.StatusOK, w.Code)

	// Get users list - returns array directly, not wrapped in {"users": ...}
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID)+"/users", nil, ownerToken)
	assert.Equal(t, http.StatusOK, w.Code)

	var users []interface{}
	err = json.Unmarshal(w.Body.Bytes(), &users)
	require.NoError(t, err)
	assert.Len(t, users, 2) // owner + shared user
}

// ============ Task Flow Tests ============

func TestIntegration_TaskFlow(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")

	// Create residence first
	residenceBody := map[string]interface{}{"name": "Task House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	// 1. Create a task
	taskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Fix leaky faucet",
		"description":  "Kitchen faucet is dripping",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
	assert.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := taskData["id"].(float64)
	assert.NotZero(t, taskID)
	assert.Equal(t, "Fix leaky faucet", taskData["title"])

	// 2. Get the task
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	// 3. Update the task
	updateBody := map[string]interface{}{
		"title":       "Fix kitchen faucet",
		"description": "Updated description",
	}
	w = app.makeAuthenticatedRequest(t, "PUT", "/api/tasks/"+formatID(taskID), updateBody, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var taskUpdateResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskUpdateResp)
	taskUpdateData := taskUpdateResp["data"].(map[string]interface{})
	assert.Equal(t, "Fix kitchen faucet", taskUpdateData["title"])

	// 4. Mark as in progress
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/mark-in-progress", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var progressResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &progressResp)
	progressData := progressResp["data"].(map[string]interface{})
	assert.True(t, progressData["in_progress"].(bool))

	// 5. Complete the task
	completionBody := map[string]interface{}{
		"task_id": taskID,
		"notes":   "Fixed the faucet",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
	assert.Equal(t, http.StatusCreated, w.Code)

	var completionResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &completionResp)
	completionData := completionResp["data"].(map[string]interface{})
	completionID := completionData["id"].(float64)
	assert.NotZero(t, completionID)
	assert.Equal(t, "Fixed the faucet", completionData["notes"])

	// 6. List completions
	w = app.makeAuthenticatedRequest(t, "GET", "/api/completions?task_id="+formatID(taskID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	// 7. Archive the task
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/archive", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var archiveResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &archiveResp)
	archivedData := archiveResp["data"].(map[string]interface{})
	assert.True(t, archivedData["is_archived"].(bool))

	// 8. Unarchive the task
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/unarchive", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	// 9. Cancel the task
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/cancel", nil, token)
	assert.Equal(t, http.StatusOK, w.Code)

	var cancelResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &cancelResp)
	cancelledData := cancelResp["data"].(map[string]interface{})
	assert.True(t, cancelledData["is_cancelled"].(bool))

	// 10. Delete the task (returns 200 with message, not 204)
	w = app.makeAuthenticatedRequest(t, "DELETE", "/api/tasks/"+formatID(taskID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code)
}

func TestIntegration_TasksByResidenceKanban(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")

	// Use explicit timezone to test full timezone-aware path
	testTimezone := "America/Los_Angeles"

	// Create residence
	residenceBody := map[string]interface{}{"name": "Kanban House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	// Create multiple tasks with timezone header
	for i := 1; i <= 3; i++ {
		taskBody := map[string]interface{}{
			"residence_id": residenceID,
			"title":        "Task " + formatID(float64(i)),
		}
		w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskBody, token, testTimezone)
		require.Equal(t, http.StatusCreated, w.Code)
	}

	// Get tasks by residence (kanban view) with timezone header
	w = app.makeAuthenticatedRequestWithTimezone(t, "GET", "/api/tasks/by-residence/"+formatID(float64(residenceID)), nil, token, testTimezone)
	assert.Equal(t, http.StatusOK, w.Code)

	var kanbanResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &kanbanResp)

	columns := kanbanResp["columns"].([]interface{})
	assert.Greater(t, len(columns), 0)

	// Check column structure
	for _, col := range columns {
		column := col.(map[string]interface{})
		assert.NotEmpty(t, column["name"])
		assert.NotEmpty(t, column["display_name"])
		assert.NotNil(t, column["tasks"])
		assert.NotNil(t, column["count"])
	}
}

// ============ Lookup Data Tests ============

func TestIntegration_LookupEndpoints(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "user", "user@test.com", "Password123")

	tests := []struct {
		name     string
		endpoint string
	}{
		{"residence types", "/api/residence-types"},
		{"task categories", "/api/task-categories"},
		{"task priorities", "/api/task-priorities"},
		{"task frequencies", "/api/task-frequencies"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := app.makeAuthenticatedRequest(t, "GET", tt.endpoint, nil, token)
			assert.Equal(t, http.StatusOK, w.Code)

			// All lookup endpoints return arrays directly
			var items []interface{}
			err := json.Unmarshal(w.Body.Bytes(), &items)
			require.NoError(t, err)
			assert.Greater(t, len(items), 0)

			// Check item structure
			for _, item := range items {
				obj := item.(map[string]interface{})
				assert.NotZero(t, obj["id"])
				assert.NotEmpty(t, obj["name"])
			}
		})
	}
}

// ============ Access Control Tests ============

func TestIntegration_CrossUserAccessDenied(t *testing.T) {
	app := setupIntegrationTest(t)

	// Create two users with their own residences
	user1Token := app.registerAndLogin(t, "user1", "user1@test.com", "Password123")
	user2Token := app.registerAndLogin(t, "user2", "user2@test.com", "Password123")

	// User1 creates a residence
	residenceBody := map[string]interface{}{"name": "User1's House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, user1Token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := residenceData["id"].(float64)

	// User1 creates a task
	taskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "User1's Task",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, user1Token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := taskData["id"].(float64)

	// User2 cannot access User1's residence
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, user2Token)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// User2 cannot access User1's task
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks/"+formatID(taskID), nil, user2Token)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// User2 cannot update User1's residence
	updateBody := map[string]interface{}{"name": "Hacked!"}
	w = app.makeAuthenticatedRequest(t, "PUT", "/api/residences/"+formatID(residenceID), updateBody, user2Token)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// User2 cannot delete User1's residence
	w = app.makeAuthenticatedRequest(t, "DELETE", "/api/residences/"+formatID(residenceID), nil, user2Token)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// User2 cannot create task in User1's residence
	taskBody2 := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Malicious Task",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody2, user2Token)
	assert.Equal(t, http.StatusForbidden, w.Code)
}

// ============ JSON Response Structure Tests ============

func TestIntegration_ResponseStructure(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "user", "user@test.com", "Password123")

	// Create residence
	residenceBody := map[string]interface{}{
		"name":           "Response Test House",
		"street_address": "123 Test St",
		"city":           "Austin",
		"state_province": "TX",
		"postal_code":    "78701",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &resp)

	// Response is wrapped with "data" and "summary"
	data := resp["data"].(map[string]interface{})
	_, hasSummary := resp["summary"]
	assert.True(t, hasSummary, "Expected 'summary' field in response")

	// Verify all expected fields are present in data
	expectedFields := []string{
		"id", "owner_id", "name", "street_address", "city",
		"state_province", "postal_code", "country",
		"is_primary", "is_active", "created_at", "updated_at",
	}

	for _, field := range expectedFields {
		_, exists := data[field]
		assert.True(t, exists, "Expected field %s to be present in data", field)
	}

	// Check that nullable fields can be null
	assert.Nil(t, data["bedrooms"])
	assert.Nil(t, data["bathrooms"])
}

// ============ Comprehensive E2E Test ============

// TestIntegration_ComprehensiveE2E is a full end-to-end test that:
// 1. Registers a new user and verifies login
// 2. Creates 5 residences
// 3. Creates 20 tasks in different statuses across residences
// 4. Verifies residences return correctly
// 5. Verifies tasks return correctly
// 6. Verifies kanban categorization across 5 timezones
func TestIntegration_ComprehensiveE2E(t *testing.T) {
	app := setupIntegrationTest(t)

	// ============ Phase 1: Authentication ============
	t.Log("Phase 1: Testing authentication flow")

	// Register new user
	registerBody := map[string]string{
		"username": "e2e_testuser",
		"email":    "e2e@example.com",
		"password": "SecurePass123!",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody, "")
	require.Equal(t, http.StatusCreated, w.Code, "Registration should succeed")

	var registerResp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &registerResp)
	require.NoError(t, err)
	assert.NotEmpty(t, registerResp["token"], "Registration should return token")
	assert.NotNil(t, registerResp["user"], "Registration should return user")

	// Verify login with same credentials
	loginBody := map[string]string{
		"username": "e2e_testuser",
		"password": "SecurePass123!",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/login", loginBody, "")
	require.Equal(t, http.StatusOK, w.Code, "Login should succeed")

	var loginResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &loginResp)
	require.NoError(t, err)
	token := loginResp["token"].(string)
	assert.NotEmpty(t, token, "Login should return token")

	// Verify authenticated access
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, token)
	require.Equal(t, http.StatusOK, w.Code, "Should access protected route with valid token")

	var meResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &meResp)
	assert.Equal(t, "e2e_testuser", meResp["username"])
	assert.Equal(t, "e2e@example.com", meResp["email"])

	t.Log("✓ Authentication flow verified")

	// ============ Phase 2: Create 5 Residences ============
	t.Log("Phase 2: Creating 5 residences")

	residenceNames := []string{
		"Main House",
		"Beach House",
		"Mountain Cabin",
		"City Apartment",
		"Lake House",
	}
	residenceIDs := make([]uint, 5)

	for i, name := range residenceNames {
		createBody := map[string]interface{}{
			"name":           name,
			"street_address": fmt.Sprintf("%d Test St", (i+1)*100),
			"city":           "Austin",
			"state_province": "TX",
			"postal_code":    fmt.Sprintf("787%02d", i),
		}
		w = app.makeAuthenticatedRequest(t, "POST", "/api/residences", createBody, token)
		require.Equal(t, http.StatusCreated, w.Code, "Should create residence: %s", name)

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)
		data := resp["data"].(map[string]interface{})
		residenceIDs[i] = uint(data["id"].(float64))
		assert.Equal(t, name, data["name"])
	}

	t.Logf("✓ Created 5 residences with IDs: %v", residenceIDs)

	// ============ Phase 3: Create 20 Tasks with Various Statuses ============
	t.Log("Phase 3: Creating 20 tasks with various statuses and due dates")

	// Use a fixed reference date for consistent testing
	// This ensures tasks fall into predictable kanban columns
	now := time.Now().UTC()
	startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)

	// Task configurations: title, residenceIndex, daysFromNow, status
	taskConfigs := []struct {
		title          string
		residenceIndex int
		daysFromNow    int
		status         string // "active", "in_progress", "completed", "cancelled", "archived"
	}{
		// Overdue tasks (due before today)
		{"Overdue Task 1 - Fix roof", 0, -5, "active"},
		{"Overdue Task 2 - Repair fence", 1, -3, "active"},
		{"Overdue Task 3 - Paint garage", 2, -1, "in_progress"}, // In progress but overdue

		// Due soon tasks (today to 30 days)
		{"Due Today - Check smoke detectors", 0, 0, "active"},
		{"Due Tomorrow - Water plants", 1, 1, "active"},
		{"Due in 5 days - Clean gutters", 2, 5, "active"},
		{"Due in 10 days - Service HVAC", 3, 10, "active"},
		{"Due in 20 days - Pressure wash deck", 4, 20, "in_progress"},

		// Upcoming tasks (beyond 30 days or no due date)
		{"Due in 35 days - Annual inspection", 0, 35, "active"},
		{"Due in 45 days - Refinish floors", 1, 45, "active"},
		{"No due date - Organize garage", 2, -999, "active"}, // -999 = no due date

		// Completed tasks
		{"Completed Task 1 - Replace filters", 0, -10, "completed"},
		{"Completed Task 2 - Fix doorbell", 1, -7, "completed"},
		{"Completed Task 3 - Clean windows", 2, 5, "completed"}, // Due soon but completed

		// Cancelled tasks
		{"Cancelled Task 1 - Build shed", 3, 15, "cancelled"},
		{"Cancelled Task 2 - Install pool", 4, 60, "cancelled"},

		// Archived tasks (hidden from kanban board)
		{"Archived Task 1 - Old project", 0, -30, "archived"},
		{"Archived Task 2 - Deprecated work", 1, -20, "archived"},

		// Additional active tasks for variety
		{"Regular Task - Mow lawn", 3, 3, "active"},
		{"Regular Task - Trim hedges", 4, 7, "active"},
	}

	type createdTask struct {
		ID             uint
		Title          string
		ResidenceID    uint
		DueDate        *time.Time
		Status         string
		ExpectedColumn string
	}
	createdTasks := make([]createdTask, 0, len(taskConfigs))

	for _, cfg := range taskConfigs {
		residenceID := residenceIDs[cfg.residenceIndex]

		taskBody := map[string]interface{}{
			"residence_id": residenceID,
			"title":        cfg.title,
			"description":  fmt.Sprintf("E2E test task - %s", cfg.status),
		}

		// Set due date unless -999 (no due date)
		var dueDate *time.Time
		if cfg.daysFromNow != -999 {
			d := startOfToday.AddDate(0, 0, cfg.daysFromNow)
			dueDate = &d
			taskBody["due_date"] = d.Format(time.RFC3339)
		}

		w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
		require.Equal(t, http.StatusCreated, w.Code, "Should create task: %s", cfg.title)

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)
		data := resp["data"].(map[string]interface{})
		taskID := uint(data["id"].(float64))

		// Apply status changes
		switch cfg.status {
		case "in_progress":
			w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/mark-in-progress", taskID), nil, token)
			require.Equal(t, http.StatusOK, w.Code)

		case "completed":
			// Create a completion
			completionBody := map[string]interface{}{
				"task_id": taskID,
				"notes":   "Completed for E2E test",
			}
			w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
			require.Equal(t, http.StatusCreated, w.Code)

		case "cancelled":
			w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/cancel", taskID), nil, token)
			require.Equal(t, http.StatusOK, w.Code)

		case "archived":
			w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/archive", taskID), nil, token)
			require.Equal(t, http.StatusOK, w.Code)
		}

		// Determine expected kanban column
		expectedColumn := determineExpectedColumn(cfg.daysFromNow, cfg.status, 30)

		createdTasks = append(createdTasks, createdTask{
			ID:             taskID,
			Title:          cfg.title,
			ResidenceID:    residenceID,
			DueDate:        dueDate,
			Status:         cfg.status,
			ExpectedColumn: expectedColumn,
		})
	}

	t.Logf("✓ Created %d tasks with various statuses", len(createdTasks))

	// ============ Phase 4: Verify Residences Return Correctly ============
	t.Log("Phase 4: Verifying residences return correctly")

	// List all residences
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var residenceList []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceList)
	assert.Len(t, residenceList, 5, "Should have 5 residences")

	// Verify each residence individually
	for i, expectedName := range residenceNames {
		w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", residenceIDs[i]), nil, token)
		require.Equal(t, http.StatusOK, w.Code)

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)
		assert.Equal(t, expectedName, resp["name"], "Residence name should match")
	}

	t.Log("✓ All 5 residences verified")

	// ============ Phase 5: Verify Tasks Return Correctly ============
	t.Log("Phase 5: Verifying tasks return correctly")

	// List all tasks
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var taskListResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskListResp)

	// Count total visible tasks across all columns
	totalTasks := 0
	if columns, ok := taskListResp["columns"].([]interface{}); ok {
		for _, col := range columns {
			column := col.(map[string]interface{})
			if tasks, ok := column["tasks"].([]interface{}); ok {
				totalTasks += len(tasks)
			}
		}
	}
	expectedVisibleTasks := 0
	for _, task := range createdTasks {
		if task.ExpectedColumn != "" {
			expectedVisibleTasks++
		}
	}
	assert.Equal(t, expectedVisibleTasks, totalTasks, "Should have %d visible tasks", expectedVisibleTasks)

	// Verify individual task retrieval
	for _, task := range createdTasks {
		w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", task.ID), nil, token)
		require.Equal(t, http.StatusOK, w.Code, "Should retrieve task: %s", task.Title)

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)
		assert.Equal(t, task.Title, resp["title"], "Task title should match")
	}

	t.Log("✓ All 20 tasks verified")

	// ============ Phase 6: Kanban Verification Across 5 Timezones ============
	t.Log("Phase 6: Verifying kanban endpoint accepts X-Timezone header")

	// Test timezones spanning the extremes
	// This test verifies:
	// 1. The API correctly processes the X-Timezone header
	// 2. The kanban structure is valid for each timezone
	//
	// Note: Whether categorization actually changes depends on test runtime.
	// See TestIntegration_TimezoneDateBoundary for deterministic timezone behavior tests.
	timezones := []struct {
		name     string
		location string
		offset   string // for documentation
	}{
		{"UTC", "UTC", "UTC+0"},
		{"Tokyo", "Asia/Tokyo", "UTC+9"},
		{"Auckland", "Pacific/Auckland", "UTC+13"},
		{"NewYork", "America/New_York", "UTC-5"},
		{"Honolulu", "Pacific/Honolulu", "UTC-10"},
	}

	for _, tz := range timezones {
		t.Logf("  Testing with X-Timezone: %s (%s)", tz.location, tz.offset)

		loc, err := time.LoadLocation(tz.location)
		require.NoError(t, err, "Should load timezone: %s", tz.location)
		nowInTZ := time.Now().In(loc)

		// Query kanban WITH the X-Timezone header
		w = app.makeAuthenticatedRequestWithTimezone(t, "GET",
			fmt.Sprintf("/api/tasks/by-residence/%d", residenceIDs[0]), nil, token, tz.location)
		require.Equal(t, http.StatusOK, w.Code)

		var kanbanResp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &kanbanResp)

		// Verify kanban structure
		columns, ok := kanbanResp["columns"].([]interface{})
		require.True(t, ok, "Should have columns array")

		// Expected column names
		expectedColumns := map[string]bool{
			"overdue_tasks":     false,
			"in_progress_tasks": false,
			"due_soon_tasks":    false,
			"upcoming_tasks":    false,
			"completed_tasks":   false,
		}

		for _, col := range columns {
			column := col.(map[string]interface{})
			colName := column["name"].(string)
			expectedColumns[colName] = true

			// Verify column has required fields
			assert.NotEmpty(t, column["display_name"], "Column should have display_name")
			assert.NotNil(t, column["tasks"], "Column should have tasks array")
			assert.NotNil(t, column["count"], "Column should have count")

			// Verify count matches tasks length
			tasks := column["tasks"].([]interface{})
			count := int(column["count"].(float64))
			assert.Equal(t, len(tasks), count, "Count should match tasks length for %s", colName)
		}

		// Verify all expected columns exist
		for colName, found := range expectedColumns {
			assert.True(t, found, "Should have column: %s", colName)
		}

		t.Logf("    ✓ Kanban valid with X-Timezone=%s (local: %s)", tz.location, nowInTZ.Format("2006-01-02 15:04"))
	}

	t.Log("✓ Kanban endpoint correctly processes X-Timezone header")

	// ============ Phase 7: Verify Task Distribution in Kanban Columns ============
	t.Log("Phase 7: Verifying each task is in its expected kanban column")

	// Get full kanban view (all residences)
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var fullKanban map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &fullKanban)

	// Log distribution for debugging
	columnCounts := getColumnCounts(fullKanban)
	t.Log("  Task distribution:")
	for colName, count := range columnCounts {
		t.Logf("    %s: %d tasks", colName, count)
	}

	// IDENTITY-BASED CORRECTNESS TEST
	// Build map of column → actual task IDs from kanban response
	columnTaskIDs := make(map[string][]uint)
	columns := fullKanban["columns"].([]interface{})
	for _, col := range columns {
		column := col.(map[string]interface{})
		colName := column["name"].(string)
		tasks := column["tasks"].([]interface{})
		for _, task := range tasks {
			taskMap := task.(map[string]interface{})
			columnTaskIDs[colName] = append(columnTaskIDs[colName], uint(taskMap["id"].(float64)))
		}
	}

	// Verify each task is in expected column (or hidden for cancelled/archived)
	t.Log("  Verifying each task's column membership by ID:")
	for _, task := range createdTasks {
		if task.ExpectedColumn == "" {
			found := false
			for colName, ids := range columnTaskIDs {
				for _, id := range ids {
					if id == task.ID {
						found = true
						assert.Fail(t, "Hidden task unexpectedly visible",
							"Task ID %d ('%s') should be hidden from kanban but appeared in '%s'",
							task.ID, task.Title, colName)
						break
					}
				}
			}
			assert.False(t, found, "Task ID %d ('%s') should be hidden from kanban", task.ID, task.Title)
			if !found {
				t.Logf("    ✓ Task %d ('%s') correctly hidden from board", task.ID, task.Title)
			}
			continue
		}

		actualIDs := columnTaskIDs[task.ExpectedColumn]
		found := false
		for _, id := range actualIDs {
			if id == task.ID {
				found = true
				break
			}
		}
		assert.True(t, found, "Task ID %d ('%s') should be in column '%s' but was not found there. Column contains IDs: %v",
			task.ID, task.Title, task.ExpectedColumn, actualIDs)
		if found {
			t.Logf("    ✓ Task %d ('%s') verified in '%s'", task.ID, task.Title, task.ExpectedColumn)
		}
	}

	// Verify total equals expected visible tasks (sanity check)
	total := 0
	for _, ids := range columnTaskIDs {
		total += len(ids)
	}
	assert.Equal(t, expectedVisibleTasks, total, "Total tasks across all columns should be %d", expectedVisibleTasks)

	t.Logf("✓ All %d visible tasks verified in correct columns by ID", expectedVisibleTasks)

	// ============ Phase 9: Create User B ============
	t.Log("Phase 9: Creating User B and verifying login")

	// Register User B
	registerBodyB := map[string]string{
		"username": "e2e_userb",
		"email":    "e2e_userb@example.com",
		"password": "SecurePass456!",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBodyB, "")
	require.Equal(t, http.StatusCreated, w.Code, "User B registration should succeed")

	// Login as User B
	loginBodyB := map[string]string{
		"username": "e2e_userb",
		"password": "SecurePass456!",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/login", loginBodyB, "")
	require.Equal(t, http.StatusOK, w.Code, "User B login should succeed")

	var loginRespB map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &loginRespB)
	tokenB := loginRespB["token"].(string)
	assert.NotEmpty(t, tokenB, "User B should have a token")

	// Verify User B can access their own profile
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	var meBResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &meBResp)
	assert.Equal(t, "e2e_userb", meBResp["username"])

	t.Log("✓ User B created and verified")

	// ============ Phase 10: User A Shares Residence with User B ============
	t.Log("Phase 10: User A shares residence with User B")

	// We'll share residenceIDs[0] (Main House) with User B
	sharedResidenceID := residenceIDs[0]
	sharedResidenceName := residenceNames[0]

	// User B cannot access the residence initially
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", sharedResidenceID), nil, tokenB)
	assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT have access before sharing")

	// User A generates share code for the residence
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", sharedResidenceID), nil, token)
	require.Equal(t, http.StatusOK, w.Code, "User A should be able to generate share code")

	var shareCodeResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &shareCodeResp)
	shareCodeObj := shareCodeResp["share_code"].(map[string]interface{})
	shareCode := shareCodeObj["code"].(string)
	assert.Len(t, shareCode, 6, "Share code should be 6 characters")

	// User B joins with the share code
	joinBody := map[string]interface{}{
		"code": shareCode,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", joinBody, tokenB)
	require.Equal(t, http.StatusOK, w.Code, "User B should be able to join with share code")

	t.Logf("✓ User A shared '%s' (ID: %d) with User B using code: %s", sharedResidenceName, sharedResidenceID, shareCode)

	// ============ Phase 11: Verify User B Has Access to Shared Residence Only ============
	t.Log("Phase 11: Verifying User B has access to shared residence only")

	// User B should now be able to access the shared residence
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", sharedResidenceID), nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code, "User B should have access to shared residence")

	var sharedResidenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &sharedResidenceResp)
	assert.Equal(t, sharedResidenceName, sharedResidenceResp["name"], "Shared residence name should match")

	// User B should only see 1 residence in their list
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	var userBResidences []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBResidences)
	assert.Len(t, userBResidences, 1, "User B should only see 1 residence")
	assert.Equal(t, sharedResidenceName, userBResidences[0]["name"], "User B's only residence should be the shared one")

	// User B should NOT have access to other residences
	for i, resID := range residenceIDs {
		if resID != sharedResidenceID {
			w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", resID), nil, tokenB)
			assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT have access to residence %d (%s)", resID, residenceNames[i])
		}
	}

	t.Log("✓ User B has access to shared residence only")

	// ============ Phase 12: Verify User B Sees Tasks for Shared Residence ============
	t.Log("Phase 12: Verifying User B sees tasks for shared residence")

	// Get tasks for the shared residence as User B
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/by-residence/%d", sharedResidenceID), nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code, "User B should be able to get tasks for shared residence")

	var userBKanbanResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBKanbanResp)

	// Count tasks in User B's kanban for the shared residence
	userBTaskCount := 0
	userBColumns := userBKanbanResp["columns"].([]interface{})
	for _, col := range userBColumns {
		column := col.(map[string]interface{})
		tasks := column["tasks"].([]interface{})
		userBTaskCount += len(tasks)
	}

	// Count expected tasks for shared residence (residenceIndex=0 in our config)
	expectedTasksForResidence := 0
	for _, task := range createdTasks {
		if task.ResidenceID == sharedResidenceID && task.ExpectedColumn != "" {
			expectedTasksForResidence++
		}
	}

	assert.Equal(t, expectedTasksForResidence, userBTaskCount,
		"User B should see %d tasks for shared residence, got %d", expectedTasksForResidence, userBTaskCount)

	// User B should NOT be able to get tasks for other residences
	for _, resID := range residenceIDs {
		if resID != sharedResidenceID {
			w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/by-residence/%d", resID), nil, tokenB)
			assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT access tasks for unshared residence %d", resID)
		}
	}

	// User B's full task list should only show tasks from shared residence
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	var userBFullKanban map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBFullKanban)

	totalUserBTasks := 0
	fullColumns := userBFullKanban["columns"].([]interface{})
	for _, col := range fullColumns {
		column := col.(map[string]interface{})
		tasks := column["tasks"].([]interface{})
		totalUserBTasks += len(tasks)
	}

	assert.Equal(t, expectedTasksForResidence, totalUserBTasks,
		"User B's full task list should only contain %d tasks from shared residence", expectedTasksForResidence)

	t.Logf("✓ User B sees %d tasks for shared residence", userBTaskCount)

	// ============ Phase 13: Test User B Kanban Across Different Timezones ============
	t.Log("Phase 13: Verifying User B kanban with X-Timezone header")

	// Test that User B's kanban endpoint accepts X-Timezone header
	for _, tz := range timezones {
		t.Logf("  Testing User B with X-Timezone: %s (%s)", tz.location, tz.offset)

		loc, err := time.LoadLocation(tz.location)
		require.NoError(t, err)
		nowInTZ := time.Now().In(loc)

		// Get User B's kanban WITH X-Timezone header
		w = app.makeAuthenticatedRequestWithTimezone(t, "GET",
			fmt.Sprintf("/api/tasks/by-residence/%d", sharedResidenceID), nil, tokenB, tz.location)
		require.Equal(t, http.StatusOK, w.Code)

		var tzKanbanResp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &tzKanbanResp)

		// Verify kanban structure
		tzColumns := tzKanbanResp["columns"].([]interface{})
		require.NotEmpty(t, tzColumns, "Should have columns")

		// Verify all expected columns exist
		foundColumns := make(map[string]bool)
		for _, col := range tzColumns {
			column := col.(map[string]interface{})
			colName := column["name"].(string)
			foundColumns[colName] = true

			// Verify column has required fields
			assert.NotEmpty(t, column["display_name"])
			assert.NotNil(t, column["tasks"])
			assert.NotNil(t, column["count"])
		}

		expectedColumnNames := []string{
			"overdue_tasks", "in_progress_tasks", "due_soon_tasks",
			"upcoming_tasks", "completed_tasks",
		}
		for _, colName := range expectedColumnNames {
			assert.True(t, foundColumns[colName], "User B should have column: %s", colName)
		}

		t.Logf("    ✓ User B kanban valid with X-Timezone=%s (local: %s)", tz.location, nowInTZ.Format("2006-01-02 15:04"))
	}

	t.Log("✓ User B kanban correctly processes X-Timezone header")

	// ============ Phase 14: User A Creates Contractors, Verify User B Access ============
	t.Log("Phase 14: User A creates contractors, verifying User B access")

	// User A creates 5 contractors, one for each residence
	contractorNames := []string{
		"Main House Plumber",
		"Beach House Electrician",
		"Mountain Cabin Roofer",
		"City Apartment HVAC",
		"Lake House Landscaper",
	}
	contractorIDs := make([]uint, 5)

	for i, name := range contractorNames {
		residenceID := residenceIDs[i]
		contractorBody := map[string]interface{}{
			"residence_id": residenceID,
			"name":         name,
			"company":      fmt.Sprintf("%s Inc.", name),
			"phone":        fmt.Sprintf("555-000-%04d", i+1),
			"email":        fmt.Sprintf("contractor%d@example.com", i+1),
		}

		w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorBody, token)
		require.Equal(t, http.StatusCreated, w.Code, "User A should create contractor: %s", name)

		// Contractor API returns the object directly without "data" wrapper
		var contractorResp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &contractorResp)
		contractorIDs[i] = uint(contractorResp["id"].(float64))

		t.Logf("  Created contractor '%s' for residence '%s'", name, residenceNames[i])
	}

	t.Logf("✓ User A created 5 contractors with IDs: %v", contractorIDs)

	// Verify User A can see all 5 contractors
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var userAContractors []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userAContractors)
	assert.Len(t, userAContractors, 5, "User A should see all 5 contractors")

	// User B should only see contractors for the shared residence
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	var userBContractors []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBContractors)
	assert.Len(t, userBContractors, 1, "User B should see only 1 contractor (from shared residence)")

	// Verify User B's contractor is from the shared residence
	if len(userBContractors) > 0 {
		userBContractor := userBContractors[0]
		assert.Equal(t, contractorNames[0], userBContractor["name"], "User B's contractor should be '%s'", contractorNames[0])
	}

	// Verify User B can access the shared residence's contractor directly
	sharedContractorID := contractorIDs[0]
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/%d", sharedContractorID), nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code, "User B should access contractor for shared residence")

	// Verify User B cannot access contractors for other residences
	for i, contractorID := range contractorIDs {
		if i != 0 { // Skip the shared residence's contractor
			w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/%d", contractorID), nil, tokenB)
			assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT access contractor %d (%s)", contractorID, contractorNames[i])
		}
	}

	// Verify User B can list contractors by shared residence
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/by-residence/%d", sharedResidenceID), nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code, "User B should list contractors for shared residence")

	var userBResidenceContractors []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBResidenceContractors)
	assert.Len(t, userBResidenceContractors, 1, "User B should see 1 contractor for shared residence")

	// Verify User B cannot list contractors for other residences
	for i, resID := range residenceIDs {
		if resID != sharedResidenceID {
			w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/by-residence/%d", resID), nil, tokenB)
			assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT list contractors for residence %d (%s)", resID, residenceNames[i])
		}
	}

	t.Log("✓ User B contractor access verified correctly")

	// ============ Phase 15: Final Summary ============
	t.Log("\n========== E2E Test Summary ==========")
	t.Log("✓ User A registration and login")
	t.Log("✓ 5 residences created and verified")
	t.Log("✓ 20 tasks created with various statuses")
	t.Log("✓ Tasks correctly distributed in kanban columns")
	t.Log("✓ Kanban structure verified across 5 timezones (User A)")
	t.Log("✓ User B registration and login")
	t.Log("✓ Residence sharing from User A to User B")
	t.Log("✓ User B access limited to shared residence only")
	t.Log("✓ User B sees only tasks for shared residence")
	t.Log("✓ User B kanban verified across 5 timezones")
	t.Log("✓ 5 contractors created (one per residence)")
	t.Log("✓ User B access limited to contractor for shared residence")
	t.Log("========================================")
}

// determineExpectedColumn determines which kanban column a task should be in
// based on its due date offset, status, and threshold
func determineExpectedColumn(daysFromNow int, status string, threshold int) string {
	// This must match the categorization chain priority order:
	// Cancelled and archived tasks are intentionally hidden from kanban board view.
	// Remaining visible columns follow:
	// 1. Completed
	// 2. InProgress (takes precedence over date-based columns)
	// 3. Overdue
	// 4. DueSoon
	// 5. Upcoming
	switch status {
	case "cancelled", "archived":
		return "" // Hidden from board
	case "completed":
		return "completed_tasks"
	case "in_progress":
		// In-progress ALWAYS goes to in_progress column because it has
		// higher priority (4) than overdue (5) in the categorization chain
		return "in_progress_tasks"
	default: // "active"
		if daysFromNow == -999 {
			return "upcoming_tasks" // No due date
		}
		if daysFromNow < 0 {
			return "overdue_tasks"
		}
		if daysFromNow < threshold {
			return "due_soon_tasks"
		}
		return "upcoming_tasks"
	}
}

// ============ Helper Functions ============

func formatID(id float64) string {
	return fmt.Sprintf("%d", uint(id))
}

// setupContractorTest sets up a test environment including contractor routes
func setupContractorTest(t *testing.T) *TestApp {
	// Echo does not need test mode

	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)

	// Create repositories
	userRepo := repositories.NewUserRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)

	// Create config
	cfg := &config.Config{
		Security: config.SecurityConfig{
			SecretKey:            "test-secret-key-for-integration-tests",
			PasswordResetExpiry:  15 * time.Minute,
			ConfirmationExpiry:   24 * time.Hour,
			MaxPasswordResetRate: 3,
		},
	}

	// Create services
	authService := services.NewAuthService(userRepo, cfg)
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	taskService := services.NewTaskService(taskRepo, residenceRepo)
	contractorService := services.NewContractorService(contractorRepo, residenceRepo)

	// Create handlers
	authHandler := handlers.NewAuthHandler(authService, nil, nil)
	residenceHandler := handlers.NewResidenceHandler(residenceService, nil, nil, true)
	taskHandler := handlers.NewTaskHandler(taskService, nil)
	contractorHandler := handlers.NewContractorHandler(contractorService)

	// Create router with real middleware
	e := echo.New()
	e.Validator = validator.NewCustomValidator()
	e.HTTPErrorHandler = apperrors.HTTPErrorHandler

	// Add timezone middleware globally so X-Timezone header is processed
	e.Use(middleware.TimezoneMiddleware())

	// Public routes
	auth := e.Group("/api/auth")
	{
		auth.POST("/register", authHandler.Register)
		auth.POST("/login", authHandler.Login)
	}

	// Protected routes
	authMiddleware := middleware.NewAuthMiddleware(db, nil)
	api := e.Group("/api")
	api.Use(authMiddleware.TokenAuth())
	{
		api.GET("/auth/me", authHandler.CurrentUser)
		api.POST("/auth/logout", authHandler.Logout)

		residences := api.Group("/residences")
		{
			residences.GET("", residenceHandler.ListResidences)
			residences.POST("", residenceHandler.CreateResidence)
			residences.GET("/:id", residenceHandler.GetResidence)
			residences.PUT("/:id", residenceHandler.UpdateResidence)
			residences.DELETE("/:id", residenceHandler.DeleteResidence)
			residences.POST("/:id/generate-share-code", residenceHandler.GenerateShareCode)
			residences.GET("/:id/users", residenceHandler.GetResidenceUsers)
			residences.DELETE("/:id/users/:user_id", residenceHandler.RemoveResidenceUser)
		}
		api.POST("/residences/join-with-code", residenceHandler.JoinWithCode)

		tasks := api.Group("/tasks")
		{
			tasks.GET("", taskHandler.ListTasks)
			tasks.POST("", taskHandler.CreateTask)
			tasks.GET("/:id", taskHandler.GetTask)
			tasks.PUT("/:id", taskHandler.UpdateTask)
			tasks.DELETE("/:id", taskHandler.DeleteTask)
		}

		contractors := api.Group("/contractors")
		{
			contractors.GET("", contractorHandler.ListContractors)
			contractors.POST("", contractorHandler.CreateContractor)
			contractors.GET("/:id", contractorHandler.GetContractor)
			contractors.PUT("/:id", contractorHandler.UpdateContractor)
			contractors.DELETE("/:id", contractorHandler.DeleteContractor)
		}
	}

	return &TestApp{
		DB:                db,
		Router:            e,
		AuthHandler:       authHandler,
		ResidenceHandler:  residenceHandler,
		TaskHandler:       taskHandler,
		ContractorHandler: contractorHandler,
		UserRepo:          userRepo,
		ResidenceRepo:     residenceRepo,
		TaskRepo:          taskRepo,
		ContractorRepo:    contractorRepo,
		AuthService:       authService,
	}
}

// ============ Test 1: Recurring Task Lifecycle ============

// TestIntegration_RecurringTaskLifecycle tests the complete lifecycle of recurring tasks:
// - Create tasks with different frequencies (once, weekly, monthly)
// - Complete each task multiple times
// - Verify NextDueDate advances correctly
// - Verify task moves between kanban columns appropriately
func TestIntegration_RecurringTaskLifecycle(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "recurring_user", "recurring@test.com", "Password123")

	// Create residence
	residenceBody := map[string]interface{}{"name": "Recurring Task House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	now := time.Now().UTC()
	startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)

	t.Log("Phase 1: Creating tasks with different frequencies")

	// Frequency IDs from seeded data:
	// 1 = Once (nil days)
	// 2 = Weekly (7 days)
	// 3 = Monthly (30 days)
	frequencyOnce := uint(1)
	frequencyWeekly := uint(2)
	frequencyMonthly := uint(3)

	// Create one-time task (due today)
	oneTimeTaskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "One-Time Task",
		"due_date":     startOfToday.Format(time.RFC3339),
		"frequency_id": frequencyOnce,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", oneTimeTaskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var oneTimeResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &oneTimeResp)
	oneTimeData := oneTimeResp["data"].(map[string]interface{})
	oneTimeTaskID := uint(oneTimeData["id"].(float64))
	t.Logf("  Created one-time task (ID: %d)", oneTimeTaskID)

	// Create weekly recurring task (due today)
	weeklyTaskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Weekly Recurring Task",
		"due_date":     startOfToday.Format(time.RFC3339),
		"frequency_id": frequencyWeekly,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", weeklyTaskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var weeklyResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &weeklyResp)
	weeklyData := weeklyResp["data"].(map[string]interface{})
	weeklyTaskID := uint(weeklyData["id"].(float64))
	t.Logf("  Created weekly task (ID: %d)", weeklyTaskID)

	// Create monthly recurring task (due today)
	monthlyTaskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Monthly Recurring Task",
		"due_date":     startOfToday.Format(time.RFC3339),
		"frequency_id": frequencyMonthly,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", monthlyTaskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var monthlyResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &monthlyResp)
	monthlyData := monthlyResp["data"].(map[string]interface{})
	monthlyTaskID := uint(monthlyData["id"].(float64))
	t.Logf("  Created monthly task (ID: %d)", monthlyTaskID)

	t.Log("✓ All tasks created")

	// Phase 2: Complete one-time task
	t.Log("Phase 2: Complete one-time task and verify it's marked completed")

	completionBody := map[string]interface{}{
		"task_id": oneTimeTaskID,
		"notes":   "Completed one-time task",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	// Verify task is in completed column
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", oneTimeTaskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var oneTimeAfterComplete map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &oneTimeAfterComplete)

	// One-time task should have next_due_date = nil after completion
	assert.Nil(t, oneTimeAfterComplete["next_due_date"], "One-time task should have nil next_due_date after completion")
	assert.Equal(t, "completed_tasks", oneTimeAfterComplete["kanban_column"], "One-time task should be in completed column")

	t.Log("✓ One-time task completed and in completed column")

	// Phase 3: Complete weekly task multiple times
	t.Log("Phase 3: Complete weekly task and verify NextDueDate advances by 7 days")

	completionBody = map[string]interface{}{
		"task_id": weeklyTaskID,
		"notes":   "First weekly completion",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	// Verify weekly task NextDueDate advanced
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", weeklyTaskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var weeklyAfterFirst map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &weeklyAfterFirst)

	// Weekly task should have next_due_date ~7 days from now
	assert.NotNil(t, weeklyAfterFirst["next_due_date"], "Weekly task should have next_due_date after completion")
	nextDueDateStr := weeklyAfterFirst["next_due_date"].(string)
	nextDueDate, err := time.Parse(time.RFC3339, nextDueDateStr)
	require.NoError(t, err)

	// Should be approximately 7 days from today (could be +/- based on completion time)
	daysDiff := int(nextDueDate.Sub(startOfToday).Hours() / 24)
	assert.GreaterOrEqual(t, daysDiff, 6, "Next due date should be at least 6 days away")
	assert.LessOrEqual(t, daysDiff, 8, "Next due date should be at most 8 days away")

	// Weekly task should be in upcoming or due_soon column (not completed)
	kanbanColumn := weeklyAfterFirst["kanban_column"].(string)
	assert.NotEqual(t, "completed_tasks", kanbanColumn, "Weekly recurring task should NOT be in completed after first completion")

	t.Logf("✓ Weekly task NextDueDate advanced to %s (kanban: %s)", nextDueDateStr, kanbanColumn)

	// Phase 4: Complete monthly task
	t.Log("Phase 4: Complete monthly task and verify NextDueDate advances by 30 days")

	completionBody = map[string]interface{}{
		"task_id": monthlyTaskID,
		"notes":   "Monthly completion",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", monthlyTaskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var monthlyAfterComplete map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &monthlyAfterComplete)

	assert.NotNil(t, monthlyAfterComplete["next_due_date"], "Monthly task should have next_due_date after completion")
	monthlyNextDueStr := monthlyAfterComplete["next_due_date"].(string)
	monthlyNextDue, err := time.Parse(time.RFC3339, monthlyNextDueStr)
	require.NoError(t, err)

	monthlyDaysDiff := int(monthlyNextDue.Sub(startOfToday).Hours() / 24)
	assert.GreaterOrEqual(t, monthlyDaysDiff, 29, "Monthly next due date should be at least 29 days away")
	assert.LessOrEqual(t, monthlyDaysDiff, 31, "Monthly next due date should be at most 31 days away")

	t.Logf("✓ Monthly task NextDueDate advanced to %s", monthlyNextDueStr)

	// Phase 5: Verify kanban distribution
	t.Log("Phase 5: Verify kanban column distribution")

	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var kanbanResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &kanbanResp)

	columns := kanbanResp["columns"].([]interface{})
	columnTasks := make(map[string]int)
	for _, col := range columns {
		column := col.(map[string]interface{})
		colName := column["name"].(string)
		tasks := column["tasks"].([]interface{})
		columnTasks[colName] = len(tasks)
	}

	// One-time task should be completed
	assert.Equal(t, 1, columnTasks["completed_tasks"], "Should have 1 completed task (one-time)")

	// Weekly task (due in 7 days) is within 30-day threshold, so it's in due_soon
	// Monthly task (due in ~30 days) is at/beyond threshold, so it's in upcoming
	assert.Equal(t, 1, columnTasks["due_soon_tasks"], "Should have 1 due_soon task (weekly - 7 days)")
	assert.Equal(t, 1, columnTasks["upcoming_tasks"], "Should have 1 upcoming task (monthly - 30 days)")

	t.Log("✓ Kanban distribution verified")
	t.Log("\n========== Recurring Task Lifecycle Test Complete ==========")
}

// ============ Test 2: Multi-User Complex Sharing ============

// TestIntegration_MultiUserSharing tests complex sharing scenarios with multiple users:
// - 3 users with various residence sharing combinations
// - Verify each user sees only their accessible residences/tasks
// - Test user removal from shared residences
func TestIntegration_MultiUserSharing(t *testing.T) {
	app := setupIntegrationTest(t)

	t.Log("Phase 1: Create 3 users")

	tokenA := app.registerAndLogin(t, "user_a", "usera@test.com", "Password123")
	tokenB := app.registerAndLogin(t, "user_b", "userb@test.com", "Password123")
	tokenC := app.registerAndLogin(t, "user_c", "userc@test.com", "Password123")

	t.Log("✓ Created users A, B, and C")

	// Phase 2: User A creates 3 residences
	t.Log("Phase 2: User A creates 3 residences")

	residenceNames := []string{"Residence 1 (shared with B)", "Residence 2 (shared with C)", "Residence 3 (shared with B and C)"}
	residenceIDs := make([]uint, 3)

	for i, name := range residenceNames {
		createBody := map[string]interface{}{"name": name}
		w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", createBody, tokenA)
		require.Equal(t, http.StatusCreated, w.Code)

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)
		data := resp["data"].(map[string]interface{})
		residenceIDs[i] = uint(data["id"].(float64))
	}

	t.Logf("✓ Created residences with IDs: %v", residenceIDs)

	// Phase 3: Create tasks in each residence
	t.Log("Phase 3: Create tasks in each residence")

	for i, resID := range residenceIDs {
		taskBody := map[string]interface{}{
			"residence_id": resID,
			"title":        fmt.Sprintf("Task for Residence %d", i+1),
		}
		w := app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, tokenA)
		require.Equal(t, http.StatusCreated, w.Code)
	}

	t.Log("✓ Created tasks in all residences")

	// Phase 4: Share residence 1 with B only
	t.Log("Phase 4: Share residence 1 with User B")

	w := app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", residenceIDs[0]), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	var shareResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCode1 := shareResp["share_code"].(map[string]interface{})["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode1}, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ Residence 1 shared with User B")

	// Phase 5: Share residence 2 with C only
	t.Log("Phase 5: Share residence 2 with User C")

	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", residenceIDs[1]), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCode2 := shareResp["share_code"].(map[string]interface{})["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode2}, tokenC)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ Residence 2 shared with User C")

	// Phase 6: Share residence 3 with both B and C
	t.Log("Phase 6: Share residence 3 with both Users B and C")

	// Share with B
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", residenceIDs[2]), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)
	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCode3B := shareResp["share_code"].(map[string]interface{})["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode3B}, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	// Share with C
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", residenceIDs[2]), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)
	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCode3C := shareResp["share_code"].(map[string]interface{})["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode3C}, tokenC)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ Residence 3 shared with both Users B and C")

	// Phase 7: Verify each user sees correct residences
	t.Log("Phase 7: Verify residence visibility for each user")

	// User A sees all 3
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)
	var userAResidences []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userAResidences)
	assert.Len(t, userAResidences, 3, "User A should see 3 residences")

	// User B sees residence 1 and 3
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	var userBResidences []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBResidences)
	assert.Len(t, userBResidences, 2, "User B should see 2 residences (1 and 3)")

	// User C sees residence 2 and 3
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, tokenC)
	require.Equal(t, http.StatusOK, w.Code)
	var userCResidences []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userCResidences)
	assert.Len(t, userCResidences, 2, "User C should see 2 residences (2 and 3)")

	t.Log("✓ All users see correct residences")

	// Phase 8: Verify task visibility
	t.Log("Phase 8: Verify task visibility for each user")

	// User B should see 2 tasks (from residence 1 and 3)
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	var userBTasks map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBTasks)
	userBTaskCount := countTasksInKanban(userBTasks)
	assert.Equal(t, 2, userBTaskCount, "User B should see 2 tasks")

	// User C should see 2 tasks (from residence 2 and 3)
	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, tokenC)
	require.Equal(t, http.StatusOK, w.Code)
	var userCTasks map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userCTasks)
	userCTaskCount := countTasksInKanban(userCTasks)
	assert.Equal(t, 2, userCTaskCount, "User C should see 2 tasks")

	t.Log("✓ All users see correct tasks")

	// Phase 9: Remove User B from residence 3
	t.Log("Phase 9: Remove User B from residence 3")

	// Get User B's ID
	w = app.makeAuthenticatedRequest(t, "GET", "/api/auth/me", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	var userBInfo map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBInfo)
	userBID := uint(userBInfo["id"].(float64))

	// Remove User B from residence 3
	w = app.makeAuthenticatedRequest(t, "DELETE", fmt.Sprintf("/api/residences/%d/users/%d", residenceIDs[2], userBID), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ User B removed from residence 3")

	// Phase 10: Verify User B lost access to residence 3
	t.Log("Phase 10: Verify User B lost access to residence 3, C still has access")

	// User B should now only see residence 1
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	json.Unmarshal(w.Body.Bytes(), &userBResidences)
	assert.Len(t, userBResidences, 1, "User B should now see only 1 residence")

	// User B cannot access residence 3
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", residenceIDs[2]), nil, tokenB)
	assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT have access to residence 3")

	// User C still has access to residence 3
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", residenceIDs[2]), nil, tokenC)
	assert.Equal(t, http.StatusOK, w.Code, "User C should still have access to residence 3")

	t.Log("✓ User B lost access, User C retained access")

	// Phase 11: Verify User B only sees 1 task now
	t.Log("Phase 11: Verify User B now sees only 1 task")

	w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks", nil, tokenB)
	require.Equal(t, http.StatusOK, w.Code)
	json.Unmarshal(w.Body.Bytes(), &userBTasks)
	userBTaskCount = countTasksInKanban(userBTasks)
	assert.Equal(t, 1, userBTaskCount, "User B should now see only 1 task")

	t.Log("✓ User B task count updated correctly")
	t.Log("\n========== Multi-User Sharing Test Complete ==========")
}

// ============ Test 3: Task State Transitions ============

// TestIntegration_TaskStateTransitions tests all valid task state transitions:
// - Create → in_progress → complete → archive → unarchive
// - Create → cancel → uncancel
// - Verify kanban column changes with each transition
func TestIntegration_TaskStateTransitions(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "state_user", "state@test.com", "Password123")

	// Create residence
	residenceBody := map[string]interface{}{"name": "State Transition House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	now := time.Now().UTC()
	startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)

	t.Log("Phase 1: Create task (should be in due_soon)")

	taskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "State Transition Task",
		"due_date":     startOfToday.AddDate(0, 0, 5).Format(time.RFC3339), // Due in 5 days
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := uint(taskData["id"].(float64))

	assert.Equal(t, "due_soon_tasks", taskData["kanban_column"], "New task should be in due_soon")
	t.Logf("✓ Task created (ID: %d) in due_soon_tasks", taskID)

	// Phase 2: Mark in progress
	t.Log("Phase 2: Mark task as in_progress")

	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/mark-in-progress", taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var inProgressResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &inProgressResp)
	inProgressData := inProgressResp["data"].(map[string]interface{})

	assert.True(t, inProgressData["in_progress"].(bool), "Task should be marked as in_progress")
	assert.Equal(t, "in_progress_tasks", inProgressData["kanban_column"], "Task should be in in_progress column")
	t.Log("✓ Task marked in_progress")

	// Phase 3: Complete the task
	t.Log("Phase 3: Complete the task")

	completionBody := map[string]interface{}{
		"task_id": taskID,
		"notes":   "Completed for state transition test",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	// Get task to verify state
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var completedResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &completedResp)

	assert.Equal(t, "completed_tasks", completedResp["kanban_column"], "Completed task should be in completed column")
	assert.False(t, completedResp["in_progress"].(bool), "Completed task should not be in_progress")
	t.Log("✓ Task completed and in completed column")

	// Phase 4: Archive the task
	t.Log("Phase 4: Archive the task")

	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/archive", taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var archivedResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &archivedResp)
	archivedData := archivedResp["data"].(map[string]interface{})

	assert.True(t, archivedData["is_archived"].(bool), "Task should be archived")
	// Archived tasks should go to cancelled_tasks column (archived = hidden from main view)
	assert.Equal(t, "cancelled_tasks", archivedData["kanban_column"], "Archived task should be in cancelled column")
	t.Log("✓ Task archived")

	// Phase 5: Unarchive the task
	t.Log("Phase 5: Unarchive the task")

	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/unarchive", taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var unarchivedResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &unarchivedResp)
	unarchivedData := unarchivedResp["data"].(map[string]interface{})

	assert.False(t, unarchivedData["is_archived"].(bool), "Task should not be archived")
	// After unarchive, it should return to completed (since it was completed before archiving)
	assert.Equal(t, "completed_tasks", unarchivedData["kanban_column"], "Unarchived completed task should return to completed")
	t.Log("✓ Task unarchived")

	// Phase 6: Test cancel flow with a new task
	t.Log("Phase 6: Create new task and test cancel/uncancel")

	taskBody2 := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Cancel Test Task",
		"due_date":     startOfToday.AddDate(0, 0, 10).Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody2, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp2 map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp2)
	taskData2 := taskResp2["data"].(map[string]interface{})
	taskID2 := uint(taskData2["id"].(float64))

	// Cancel the task
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/cancel", taskID2), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var cancelledResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &cancelledResp)
	cancelledData := cancelledResp["data"].(map[string]interface{})

	assert.True(t, cancelledData["is_cancelled"].(bool), "Task should be cancelled")
	assert.Equal(t, "cancelled_tasks", cancelledData["kanban_column"], "Cancelled task should be in cancelled column")
	t.Log("✓ Task cancelled")

	// Uncancel the task
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/uncancel", taskID2), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var uncancelledResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &uncancelledResp)
	uncancelledData := uncancelledResp["data"].(map[string]interface{})

	assert.False(t, uncancelledData["is_cancelled"].(bool), "Task should not be cancelled")
	assert.Equal(t, "due_soon_tasks", uncancelledData["kanban_column"], "Uncancelled task should return to due_soon")
	t.Log("✓ Task uncancelled")

	// Phase 7: Test trying to cancel already cancelled task (should fail)
	t.Log("Phase 7: Verify cannot cancel already cancelled task")

	// First cancel it
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/cancel", taskID2), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	// Try to cancel again
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/tasks/%d/cancel", taskID2), nil, token)
	assert.Equal(t, http.StatusBadRequest, w.Code, "Should not be able to cancel already cancelled task")
	t.Log("✓ Correctly prevented double cancellation")

	// Phase 8: Delete a task
	t.Log("Phase 8: Delete a task")

	w = app.makeAuthenticatedRequest(t, "DELETE", fmt.Sprintf("/api/tasks/%d", taskID2), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	// Verify task is deleted
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", taskID2), nil, token)
	assert.Equal(t, http.StatusNotFound, w.Code, "Deleted task should not be found")
	t.Log("✓ Task deleted")

	t.Log("\n========== Task State Transitions Test Complete ==========")
}

// ============ Test 4: Date Boundary Edge Cases ============

// TestIntegration_DateBoundaryEdgeCases tests edge cases around date boundaries:
// - Task due today (should be due_soon, not overdue)
// - Task due yesterday (should be overdue)
// - Task due at threshold boundary (day 30)
// - Task due at day 31 (should be upcoming)
//
// IMPORTANT: This test uses an explicit timezone (America/New_York) to ensure
// we're testing the full timezone-aware path, not just UTC defaults.
func TestIntegration_DateBoundaryEdgeCases(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "boundary_user", "boundary@test.com", "Password123")

	// Create residence
	residenceBody := map[string]interface{}{"name": "Boundary Test House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	// Use a specific timezone to test the full timezone-aware path
	// All requests will use X-Timezone: America/New_York
	testTimezone := "America/New_York"
	loc, _ := time.LoadLocation(testTimezone)
	now := time.Now().In(loc)

	// Due dates are stored as UTC midnight (calendar dates)
	// "Today" in the test timezone determines what's overdue vs due_soon
	startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
	threshold := 30

	t.Logf("Testing with timezone: %s (local date: %s)", testTimezone, now.Format("2006-01-02"))

	t.Log("Phase 1: Task due today (should be due_soon, NOT overdue)")

	taskToday := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Due Today Task",
		"due_date":     startOfToday.Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskToday, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var todayResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &todayResp)
	todayData := todayResp["data"].(map[string]interface{})

	assert.Equal(t, "due_soon_tasks", todayData["kanban_column"], "Task due today should be in due_soon (not overdue)")
	t.Log("✓ Task due today correctly in due_soon")

	// Phase 2: Task due yesterday (should be overdue)
	t.Log("Phase 2: Task due yesterday (should be overdue)")

	taskYesterday := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Due Yesterday Task",
		"due_date":     startOfToday.AddDate(0, 0, -1).Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskYesterday, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var yesterdayResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &yesterdayResp)
	yesterdayData := yesterdayResp["data"].(map[string]interface{})

	assert.Equal(t, "overdue_tasks", yesterdayData["kanban_column"], "Task due yesterday should be overdue")
	t.Log("✓ Task due yesterday correctly in overdue")

	// Phase 3: Task due at threshold-1 (day 29, should be due_soon)
	t.Log("Phase 3: Task due at threshold-1 (day 29, should be due_soon)")

	taskDay29 := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Due in 29 Days Task",
		"due_date":     startOfToday.AddDate(0, 0, threshold-1).Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskDay29, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var day29Resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &day29Resp)
	day29Data := day29Resp["data"].(map[string]interface{})

	assert.Equal(t, "due_soon_tasks", day29Data["kanban_column"], "Task due in 29 days should be due_soon")
	t.Log("✓ Task at threshold-1 correctly in due_soon")

	// Phase 4: Task due exactly at threshold (day 30, should be upcoming - exclusive boundary)
	t.Log("Phase 4: Task due exactly at threshold (day 30, should be upcoming)")

	taskDay30 := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Due in 30 Days Task",
		"due_date":     startOfToday.AddDate(0, 0, threshold).Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskDay30, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var day30Resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &day30Resp)
	day30Data := day30Resp["data"].(map[string]interface{})

	assert.Equal(t, "upcoming_tasks", day30Data["kanban_column"], "Task due exactly at threshold should be upcoming")
	t.Log("✓ Task at threshold correctly in upcoming")

	// Phase 5: Task due beyond threshold (day 31, should be upcoming)
	t.Log("Phase 5: Task due beyond threshold (day 31, should be upcoming)")

	taskDay31 := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Due in 31 Days Task",
		"due_date":     startOfToday.AddDate(0, 0, threshold+1).Format(time.RFC3339),
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskDay31, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var day31Resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &day31Resp)
	day31Data := day31Resp["data"].(map[string]interface{})

	assert.Equal(t, "upcoming_tasks", day31Data["kanban_column"], "Task due in 31 days should be upcoming")
	t.Log("✓ Task beyond threshold correctly in upcoming")

	// Phase 6: Task with no due date (should be upcoming)
	t.Log("Phase 6: Task with no due date (should be upcoming)")

	taskNoDue := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "No Due Date Task",
	}
	w = app.makeAuthenticatedRequestWithTimezone(t, "POST", "/api/tasks", taskNoDue, token, testTimezone)
	require.Equal(t, http.StatusCreated, w.Code)

	var noDueResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &noDueResp)
	noDueData := noDueResp["data"].(map[string]interface{})

	assert.Equal(t, "upcoming_tasks", noDueData["kanban_column"], "Task with no due date should be upcoming")
	t.Log("✓ Task with no due date correctly in upcoming")

	// Phase 7: Verify kanban distribution (using same timezone)
	t.Log("Phase 7: Verify final kanban distribution")

	w = app.makeAuthenticatedRequestWithTimezone(t, "GET", fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, token, testTimezone)
	require.Equal(t, http.StatusOK, w.Code)

	var kanbanResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &kanbanResp)

	columnCounts := getColumnCounts(kanbanResp)

	assert.Equal(t, 1, columnCounts["overdue_tasks"], "Should have 1 overdue task (yesterday)")
	assert.Equal(t, 2, columnCounts["due_soon_tasks"], "Should have 2 due_soon tasks (today, day 29)")
	assert.Equal(t, 3, columnCounts["upcoming_tasks"], "Should have 3 upcoming tasks (day 30, day 31, no due)")

	t.Log("✓ Final kanban distribution verified")
	t.Log("\n========== Date Boundary Edge Cases Test Complete ==========")
}

// ============ Test 4b: Timezone Divergence ============

// TestIntegration_TimezoneDivergence proves that timezone affects task categorization.
// This is the key timezone behavior test - same task appears in different columns
// depending on the X-Timezone header.
//
// Strategy: Create a task due "today" (in UTC terms), then query from two timezones:
// - One where it's still "today" → task is due_soon
// - One where it's already "tomorrow" → task is overdue (due date was "yesterday")
func TestIntegration_TimezoneDivergence(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "tz_user", "tz@test.com", "Password123")

	// Create residence
	residenceBody := map[string]interface{}{"name": "Timezone Test House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	// Find two timezones where the current date differs
	// We need: timezone A where today = X, timezone B where today = X+1
	// This happens when: UTC time + offset(B) crosses midnight but UTC time + offset(A) doesn't
	utcNow := time.Now().UTC()

	// Auckland is UTC+13 - if UTC hour >= 11, Auckland is on the next day
	// Honolulu is UTC-10 - always same day or behind UTC
	//
	// If UTC is 14:00:
	//   - Honolulu: 04:00 same day (14-10=4)
	//   - Auckland: 03:00 next day (14+13-24=3)
	//
	// We'll use this to create a divergence scenario

	// Determine which date Auckland sees vs Honolulu
	aucklandNow := utcNow.In(time.FixedZone("Auckland", 13*3600))
	honoluluNow := utcNow.In(time.FixedZone("Honolulu", -10*3600))

	aucklandDate := aucklandNow.Format("2006-01-02")
	honoluluDate := honoluluNow.Format("2006-01-02")

	t.Logf("Current times - UTC: %s, Auckland: %s, Honolulu: %s",
		utcNow.Format("2006-01-02 15:04"), aucklandNow.Format("2006-01-02 15:04"), honoluluNow.Format("2006-01-02 15:04"))

	if aucklandDate == honoluluDate {
		// Dates are the same - no divergence possible at this time
		// This happens when UTC hour is between 00:00-10:59
		t.Logf("⚠️ Auckland and Honolulu see the same date (%s) - skipping divergence test", aucklandDate)
		t.Log("This test requires UTC time >= 11:00 for date divergence")

		// Still verify the timezone header is processed correctly
		taskBody := map[string]interface{}{
			"residence_id": residenceID,
			"title":        "Timezone Test Task",
			"due_date":     honoluluDate,
		}
		w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
		require.Equal(t, http.StatusCreated, w.Code)

		// Query with both timezones - should see same column
		w = app.makeAuthenticatedRequestWithTimezone(t, "GET",
			fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, token, "Pacific/Auckland")
		require.Equal(t, http.StatusOK, w.Code)
		t.Log("✓ Timezone header accepted (no divergence scenario available at current UTC time)")
		return
	}

	// We have divergence! Auckland is on a later date than Honolulu
	t.Logf("✓ Date divergence confirmed: Auckland=%s, Honolulu=%s", aucklandDate, honoluluDate)

	// Create a task due on the Honolulu date (the "earlier" date)
	// In Honolulu: this is "today" → due_soon
	// In Auckland: this is "yesterday" → overdue
	taskBody := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Date Boundary Task",
		"due_date":     honoluluDate, // Due on the earlier date
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := uint(taskData["id"].(float64))

	t.Logf("Created task ID %d due on %s", taskID, honoluluDate)

	// Query from Honolulu timezone - task should be due_soon (due "today")
	w = app.makeAuthenticatedRequestWithTimezone(t, "GET",
		fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, token, "Pacific/Honolulu")
	require.Equal(t, http.StatusOK, w.Code)

	var honoluluKanban map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &honoluluKanban)
	honoluluColumn := findTaskColumn(honoluluKanban, taskID)

	// Query from Auckland timezone - task should be overdue (due "yesterday")
	w = app.makeAuthenticatedRequestWithTimezone(t, "GET",
		fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, token, "Pacific/Auckland")
	require.Equal(t, http.StatusOK, w.Code)

	var aucklandKanban map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &aucklandKanban)
	aucklandColumn := findTaskColumn(aucklandKanban, taskID)

	t.Logf("Task column in Honolulu (where due_date is 'today'): %s", honoluluColumn)
	t.Logf("Task column in Auckland (where due_date is 'yesterday'): %s", aucklandColumn)

	// THE KEY ASSERTION: Same task, different columns based on timezone
	assert.Equal(t, "due_soon_tasks", honoluluColumn,
		"Task due 'today' in Honolulu should be in due_soon_tasks")
	assert.Equal(t, "overdue_tasks", aucklandColumn,
		"Task due 'yesterday' in Auckland should be in overdue_tasks")

	// This proves timezone handling is working correctly
	assert.NotEqual(t, honoluluColumn, aucklandColumn,
		"CRITICAL: Same task must appear in DIFFERENT columns based on timezone")

	t.Log("✓ TIMEZONE DIVERGENCE VERIFIED: Same task categorizes differently based on X-Timezone header")
	t.Logf("  - Honolulu (UTC-10, date=%s): %s", honoluluDate, honoluluColumn)
	t.Logf("  - Auckland (UTC+13, date=%s): %s", aucklandDate, aucklandColumn)
}

// findTaskColumn finds which column a task is in within a kanban response
func findTaskColumn(kanbanResp map[string]interface{}, taskID uint) string {
	columns, ok := kanbanResp["columns"].([]interface{})
	if !ok {
		return "NOT_FOUND"
	}

	for _, col := range columns {
		column := col.(map[string]interface{})
		colName := column["name"].(string)
		tasks, ok := column["tasks"].([]interface{})
		if !ok {
			continue
		}

		for _, task := range tasks {
			taskMap := task.(map[string]interface{})
			if uint(taskMap["id"].(float64)) == taskID {
				return colName
			}
		}
	}
	return "NOT_FOUND"
}

// ============ Test 5: Cascade Operations ============

// TestIntegration_CascadeOperations tests what happens when residences/tasks are deleted:
// - Create residence with tasks, completions, and contractors
// - Delete residence
// - Verify cascading effects
func TestIntegration_CascadeOperations(t *testing.T) {
	app := setupIntegrationTest(t)
	token := app.registerAndLogin(t, "cascade_user", "cascade@test.com", "Password123")

	t.Log("Phase 1: Create residence")

	residenceBody := map[string]interface{}{"name": "Cascade Test House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	t.Logf("✓ Created residence (ID: %d)", residenceID)

	// Phase 2: Create tasks
	t.Log("Phase 2: Create tasks")

	taskIDs := make([]uint, 3)
	for i := 0; i < 3; i++ {
		taskBody := map[string]interface{}{
			"residence_id": residenceID,
			"title":        fmt.Sprintf("Cascade Task %d", i+1),
		}
		w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBody, token)
		require.Equal(t, http.StatusCreated, w.Code)

		var taskResp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &taskResp)
		taskData := taskResp["data"].(map[string]interface{})
		taskIDs[i] = uint(taskData["id"].(float64))
	}

	t.Logf("✓ Created 3 tasks with IDs: %v", taskIDs)

	// Phase 3: Create completions
	t.Log("Phase 3: Create task completions")

	completionIDs := make([]uint, 2)
	for i := 0; i < 2; i++ {
		completionBody := map[string]interface{}{
			"task_id": taskIDs[i],
			"notes":   fmt.Sprintf("Completion for task %d", i+1),
		}
		w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBody, token)
		require.Equal(t, http.StatusCreated, w.Code)

		var completionResp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &completionResp)
		completionData := completionResp["data"].(map[string]interface{})
		completionIDs[i] = uint(completionData["id"].(float64))
	}

	t.Logf("✓ Created 2 completions with IDs: %v", completionIDs)

	// Phase 4: Create contractor
	t.Log("Phase 4: Create contractor")

	contractorBody := map[string]interface{}{
		"residence_id": residenceID,
		"name":         "Cascade Contractor",
		"phone":        "555-1234",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var contractorResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &contractorResp)
	contractorID := uint(contractorResp["id"].(float64))

	t.Logf("✓ Created contractor (ID: %d)", contractorID)

	// Phase 5: Verify all resources exist
	t.Log("Phase 5: Verify all resources exist before deletion")

	// Tasks exist
	for _, taskID := range taskIDs {
		w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", taskID), nil, token)
		assert.Equal(t, http.StatusOK, w.Code, "Task %d should exist", taskID)
	}

	// Completions exist
	for _, completionID := range completionIDs {
		w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/completions/%d", completionID), nil, token)
		assert.Equal(t, http.StatusOK, w.Code, "Completion %d should exist", completionID)
	}

	// Contractor exists
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/%d", contractorID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code, "Contractor should exist")

	t.Log("✓ All resources verified to exist")

	// Phase 6: Delete the residence
	t.Log("Phase 6: Delete the residence")

	w = app.makeAuthenticatedRequest(t, "DELETE", fmt.Sprintf("/api/residences/%d", residenceID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ Residence deleted")

	// Phase 7: Verify cascade effects - residence no longer accessible
	t.Log("Phase 7: Verify cascade effects")

	// Residence should not be accessible
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/residences/%d", residenceID), nil, token)
	assert.Equal(t, http.StatusForbidden, w.Code, "Deleted residence should return forbidden")

	// Tasks should not be accessible (depends on cascade behavior)
	for _, taskID := range taskIDs {
		w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", taskID), nil, token)
		// Either 404 (deleted) or 403 (residence access denied)
		assert.True(t, w.Code == http.StatusNotFound || w.Code == http.StatusForbidden,
			"Task %d should not be accessible after residence deletion", taskID)
	}

	// Contractor - behavior depends on implementation
	// May still exist but not be associated with residence, or may be deleted
	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/contractors/%d", contractorID), nil, token)
	// Could be 404, 403, or 200 depending on contractor cascade behavior
	t.Logf("  Contractor access after residence deletion: %d", w.Code)

	t.Log("✓ Cascade effects verified")
	t.Log("\n========== Cascade Operations Test Complete ==========")
}

// ============ Test 6: Multi-User Operations ============

// TestIntegration_MultiUserOperations tests two users with shared access making operations:
// - Two users can both create tasks in shared residence
// - Both can update and complete tasks
// Note: Truly concurrent operations are not tested due to SQLite limitations in test environment
func TestIntegration_MultiUserOperations(t *testing.T) {
	app := setupIntegrationTest(t)

	t.Log("Phase 1: Setup users and shared residence")

	tokenA := app.registerAndLogin(t, "multiuser_a", "multiusera@test.com", "Password123")
	tokenB := app.registerAndLogin(t, "multiuser_b", "multiuserb@test.com", "Password123")

	// User A creates residence
	residenceBody := map[string]interface{}{"name": "Multi-User Test House"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, tokenA)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := uint(residenceData["id"].(float64))

	// Share with User B
	w = app.makeAuthenticatedRequest(t, "POST", fmt.Sprintf("/api/residences/%d/generate-share-code", residenceID), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	var shareResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCode := shareResp["share_code"].(map[string]interface{})["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode}, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	t.Logf("✓ Shared residence (ID: %d) between users A and B", residenceID)

	// Phase 2: Both users create tasks (sequentially to avoid SQLite issues)
	t.Log("Phase 2: Both users create tasks")

	// User A creates a task
	taskBodyA := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Task from User A",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBodyA, tokenA)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskRespA map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskRespA)
	taskDataA := taskRespA["data"].(map[string]interface{})
	taskIDA := uint(taskDataA["id"].(float64))

	// User B creates a task
	taskBodyB := map[string]interface{}{
		"residence_id": residenceID,
		"title":        "Task from User B",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks", taskBodyB, tokenB)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskRespB map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskRespB)
	taskDataB := taskRespB["data"].(map[string]interface{})
	taskIDB := uint(taskDataB["id"].(float64))

	t.Logf("✓ Both users created tasks (A: %d, B: %d)", taskIDA, taskIDB)

	// Phase 3: Verify both tasks exist
	t.Log("Phase 3: Verify both tasks exist")

	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/by-residence/%d", residenceID), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	var kanbanResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &kanbanResp)
	totalTasks := countTasksInKanban(kanbanResp)

	assert.Equal(t, 2, totalTasks, "Should have 2 tasks")
	t.Log("✓ Both tasks created successfully")

	// Phase 4: User B can update User A's task
	t.Log("Phase 4: User B updates User A's task")

	updateBody := map[string]interface{}{
		"title": "Updated by User B",
	}
	w = app.makeAuthenticatedRequest(t, "PUT", fmt.Sprintf("/api/tasks/%d", taskIDA), updateBody, tokenB)
	require.Equal(t, http.StatusOK, w.Code)

	t.Log("✓ User B successfully updated User A's task")

	// Phase 5: Verify update
	t.Log("Phase 5: Verify task was updated")

	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/tasks/%d", taskIDA), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	var finalTaskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &finalTaskResp)

	assert.Equal(t, "Updated by User B", finalTaskResp["title"], "Task should have User B's title")
	t.Log("✓ Update verified")

	// Phase 6: Both users complete the same task
	t.Log("Phase 6: Both users add completions to the same task")

	// User A completes
	completionBodyA := map[string]interface{}{
		"task_id": taskIDB,
		"notes":   "Completed by User A",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBodyA, tokenA)
	require.Equal(t, http.StatusCreated, w.Code)

	// User B completes
	completionBodyB := map[string]interface{}{
		"task_id": taskIDB,
		"notes":   "Completed by User B",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/completions", completionBodyB, tokenB)
	require.Equal(t, http.StatusCreated, w.Code)

	t.Log("✓ Both users added completions")

	// Phase 7: Verify task has 2 completions
	t.Log("Phase 7: Verify task has 2 completions")

	w = app.makeAuthenticatedRequest(t, "GET", fmt.Sprintf("/api/completions?task_id=%d", taskIDB), nil, tokenA)
	require.Equal(t, http.StatusOK, w.Code)

	var completionsResp []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &completionsResp)
	assert.Len(t, completionsResp, 2, "Task should have 2 completions")

	t.Log("✓ Multi-user completions verified")
	t.Log("\n========== Multi-User Operations Test Complete ==========")
}

// ============ Helper Functions for New Tests ============

// countTasksInKanban counts total tasks across all kanban columns
func countTasksInKanban(kanbanResp map[string]interface{}) int {
	total := 0
	if columns, ok := kanbanResp["columns"].([]interface{}); ok {
		for _, col := range columns {
			column := col.(map[string]interface{})
			if tasks, ok := column["tasks"].([]interface{}); ok {
				total += len(tasks)
			}
		}
	}
	return total
}

// getColumnCounts returns a map of column name to task count
func getColumnCounts(kanbanResp map[string]interface{}) map[string]int {
	counts := make(map[string]int)
	if columns, ok := kanbanResp["columns"].([]interface{}); ok {
		for _, col := range columns {
			column := col.(map[string]interface{})
			colName := column["name"].(string)
			if tasks, ok := column["tasks"].([]interface{}); ok {
				counts[colName] = len(tasks)
			}
		}
	}
	return counts
}

// makeAuthenticatedRequestWithTimezone makes a request with the X-Timezone header set
func (app *TestApp) makeAuthenticatedRequestWithTimezone(t *testing.T, method, path string, body interface{}, token, timezone string) *httptest.ResponseRecorder {
	var reqBody []byte
	var err error
	if body != nil {
		reqBody, err = json.Marshal(body)
		require.NoError(t, err)
	}

	req := httptest.NewRequest(method, path, bytes.NewBuffer(reqBody))
	req.Header.Set("Content-Type", "application/json")
	if token != "" {
		req.Header.Set("Authorization", "Token "+token)
	}
	if timezone != "" {
		req.Header.Set("X-Timezone", timezone)
	}

	w := httptest.NewRecorder()
	app.Router.ServeHTTP(w, req)
	return w
}

// assertTaskInColumn verifies that a task with the given ID is in the expected kanban column
func assertTaskInColumn(t *testing.T, kanbanResp map[string]interface{}, taskID uint, expectedColumn string) {
	t.Helper()

	columns, ok := kanbanResp["columns"].([]interface{})
	require.True(t, ok, "kanban response should have columns array")

	var foundColumn string
	var found bool

	for _, col := range columns {
		column := col.(map[string]interface{})
		colName := column["name"].(string)
		tasks, ok := column["tasks"].([]interface{})
		if !ok {
			continue
		}

		for _, task := range tasks {
			taskMap := task.(map[string]interface{})
			id := uint(taskMap["id"].(float64))
			if id == taskID {
				foundColumn = colName
				found = true
				break
			}
		}
		if found {
			break
		}
	}

	require.True(t, found, "Task ID %d should be present in kanban columns", taskID)
	assert.Equal(t, expectedColumn, foundColumn, "Task ID %d should be in column '%s' but found in '%s'", taskID, expectedColumn, foundColumn)
}

// getTaskIDsInColumn returns all task IDs in a specific column
func getTaskIDsInColumn(kanbanResp map[string]interface{}, columnName string) []uint {
	var ids []uint

	columns, ok := kanbanResp["columns"].([]interface{})
	if !ok {
		return ids
	}

	for _, col := range columns {
		column := col.(map[string]interface{})
		colName := column["name"].(string)
		if colName != columnName {
			continue
		}

		tasks, ok := column["tasks"].([]interface{})
		if !ok {
			continue
		}

		for _, task := range tasks {
			taskMap := task.(map[string]interface{})
			ids = append(ids, uint(taskMap["id"].(float64)))
		}
	}

	return ids
}

// ============ Type-Safe Assertion Helpers ============

// assertTaskInColumnTyped verifies a task is in the expected column using typed structs
// Use this for better error messages and type safety
func assertTaskInColumnTyped(t *testing.T, kanban *KanbanResponse, taskID uint, expectedColumn string) {
	t.Helper()

	var foundColumn string
	var found bool

	for _, column := range kanban.Columns {
		for _, task := range column.Tasks {
			if task.ID == taskID {
				foundColumn = column.Name
				found = true
				break
			}
		}
		if found {
			break
		}
	}

	require.True(t, found, "Task ID %d should be present in kanban columns", taskID)
	assert.Equal(t, expectedColumn, foundColumn, "Task ID %d should be in column '%s' but found in '%s'", taskID, expectedColumn, foundColumn)
}

// parseKanbanResponse parses a kanban response with proper error handling
func parseKanbanResponse(t *testing.T, w *httptest.ResponseRecorder) *KanbanResponse {
	t.Helper()
	var resp KanbanResponse
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to parse kanban response: %s", w.Body.String())
	return &resp
}

// parseTaskResponse parses a task response wrapped in {"data": ...}
func parseTaskResponse(t *testing.T, w *httptest.ResponseRecorder) *TaskResponse {
	t.Helper()
	var resp DataWrapper[TaskResponse]
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to parse task response: %s", w.Body.String())
	return &resp.Data
}

// parseAuthResponse parses an auth response (login/register)
func parseAuthResponse(t *testing.T, w *httptest.ResponseRecorder) *AuthResponse {
	t.Helper()
	var resp AuthResponse
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to parse auth response: %s", w.Body.String())
	return &resp
}
