package integration

import (
	"bytes"
	"encoding/json"
	"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/models"
	"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"
)

// SubscriptionTestApp holds components for subscription integration testing
type SubscriptionTestApp struct {
	DB                  *gorm.DB
	Router              *echo.Echo
	SubscriptionService *services.SubscriptionService
	SubscriptionRepo    *repositories.SubscriptionRepository
}

func setupSubscriptionTest(t *testing.T) *SubscriptionTestApp {
	// 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)
	documentRepo := repositories.NewDocumentRepository(db)
	subscriptionRepo := repositories.NewSubscriptionRepository(db)

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

	// Create services
	authService := services.NewAuthService(userRepo, cfg)
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo)

	// Create handlers
	authHandler := handlers.NewAuthHandler(authService, nil, nil)
	residenceHandler := handlers.NewResidenceHandler(residenceService, nil, nil, true)
	subscriptionHandler := handlers.NewSubscriptionHandler(subscriptionService, nil)

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

	// 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)

		residences := api.Group("/residences")
		{
			residences.POST("", residenceHandler.CreateResidence)
		}

		subscription := api.Group("/subscription")
		{
			subscription.GET("/", subscriptionHandler.GetSubscription)
			subscription.GET("/status/", subscriptionHandler.GetSubscriptionStatus)
		}
	}

	return &SubscriptionTestApp{
		DB:                  db,
		Router:              e,
		SubscriptionService: subscriptionService,
		SubscriptionRepo:    subscriptionRepo,
	}
}

// Helper to make authenticated requests
func (app *SubscriptionTestApp) 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, nil)
	if body != nil {
		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 and user ID
func (app *SubscriptionTestApp) registerAndLogin(t *testing.T, username, email, password string) (string, uint) {
	// 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)

	token := loginResp["token"].(string)
	userMap := loginResp["user"].(map[string]interface{})
	userID := uint(userMap["id"].(float64))

	return token, userID
}

// TestIntegration_IsFreeBypassesLimitations tests that users with IsFree=true
// see limitations_enabled=false regardless of global settings
func TestIntegration_IsFreeBypassesLimitations(t *testing.T) {
	app := setupSubscriptionTest(t)

	// Register and login a user
	token, userID := app.registerAndLogin(t, "freeuser", "free@test.com", "Password123")

	// Enable global limitations - first delete any existing, then create with enabled
	app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
	settings := &models.SubscriptionSettings{EnableLimitations: true}
	err := app.DB.Create(settings).Error
	require.NoError(t, err)

	// Verify limitations are enabled globally
	var verifySettings models.SubscriptionSettings
	app.DB.First(&verifySettings)
	require.True(t, verifySettings.EnableLimitations, "Global limitations should be enabled")

	// ========== Test 1: Normal user sees limitations_enabled=true ==========
	w := app.makeAuthenticatedRequest(t, "GET", "/api/subscription/status/", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

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

	assert.True(t, statusResp["limitations_enabled"].(bool),
		"Normal user should see limitations_enabled=true when global setting is enabled")

	// ========== Test 2: Set IsFree=true for user ==========
	// Get user's subscription
	sub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)

	// Set IsFree=true
	sub.IsFree = true
	err = app.SubscriptionRepo.Update(sub)
	require.NoError(t, err)

	// ========== Test 3: User with IsFree=true sees limitations_enabled=false ==========
	w = app.makeAuthenticatedRequest(t, "GET", "/api/subscription/status/", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	err = json.Unmarshal(w.Body.Bytes(), &statusResp)
	require.NoError(t, err)

	assert.False(t, statusResp["limitations_enabled"].(bool),
		"User with IsFree=true should see limitations_enabled=false regardless of global setting")
}

// TestIntegration_IsFreeBypassesCheckLimit tests that IsFree users can create
// resources beyond their tier limits
func TestIntegration_IsFreeBypassesCheckLimit(t *testing.T) {
	app := setupSubscriptionTest(t)

	// Register and login a user
	_, userID := app.registerAndLogin(t, "limituser", "limit@test.com", "Password123")

	// Enable global limitations
	settings := &models.SubscriptionSettings{EnableLimitations: true}
	app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
	app.DB.Create(settings)

	// Set free tier limit to 1 property
	one := 1
	freeLimits := &models.TierLimits{
		Tier:            models.TierFree,
		PropertiesLimit: &one,
	}
	app.DB.Where("tier = ?", models.TierFree).Delete(&models.TierLimits{})
	app.DB.Create(freeLimits)

	// Get user's subscription (should be free tier)
	sub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)
	require.Equal(t, models.TierFree, sub.Tier)

	// ========== Test 1: Normal free user hits limit ==========
	// First property should succeed
	err = app.SubscriptionService.CheckLimit(userID, "properties")
	assert.NoError(t, err, "First property should be allowed")

	// Create a property to use up the limit
	residence := &models.Residence{
		Name:    "Test Property",
		OwnerID: userID,
	}
	app.DB.Create(residence)

	// Second property should fail
	err = app.SubscriptionService.CheckLimit(userID, "properties")
	assert.Error(t, err, "Second property should be blocked for normal free user")
	var appErr *apperrors.AppError
	require.ErrorAs(t, err, &appErr)
	assert.Equal(t, http.StatusForbidden, appErr.Code)
	assert.Equal(t, "error.properties_limit_exceeded", appErr.MessageKey)

	// ========== Test 2: Set IsFree=true ==========
	sub.IsFree = true
	err = app.SubscriptionRepo.Update(sub)
	require.NoError(t, err)

	// ========== Test 3: IsFree user bypasses limit ==========
	err = app.SubscriptionService.CheckLimit(userID, "properties")
	assert.NoError(t, err, "IsFree user should bypass property limits")

	// Should also bypass other limits
	err = app.SubscriptionService.CheckLimit(userID, "tasks")
	assert.NoError(t, err, "IsFree user should bypass task limits")

	err = app.SubscriptionService.CheckLimit(userID, "contractors")
	assert.NoError(t, err, "IsFree user should bypass contractor limits")

	err = app.SubscriptionService.CheckLimit(userID, "documents")
	assert.NoError(t, err, "IsFree user should bypass document limits")
}

// TestIntegration_IsFreeIndependentOfTier tests that IsFree works regardless of
// the user's subscription tier
func TestIntegration_IsFreeIndependentOfTier(t *testing.T) {
	app := setupSubscriptionTest(t)

	// Register and login a user
	token, userID := app.registerAndLogin(t, "tieruser", "tier@test.com", "Password123")

	// Enable global limitations
	settings := &models.SubscriptionSettings{EnableLimitations: true}
	app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
	app.DB.Create(settings)

	// Get user's subscription
	sub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)

	// ========== Test with Free tier + IsFree ==========
	sub.Tier = models.TierFree
	sub.IsFree = true
	err = app.SubscriptionRepo.Update(sub)
	require.NoError(t, err)

	w := app.makeAuthenticatedRequest(t, "GET", "/api/subscription/status/", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var statusResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &statusResp)
	assert.False(t, statusResp["limitations_enabled"].(bool),
		"Free tier user with IsFree should see limitations_enabled=false")

	// ========== Test with Pro tier + IsFree ==========
	sub.Tier = models.TierPro
	sub.IsFree = true
	err = app.SubscriptionRepo.Update(sub)
	require.NoError(t, err)

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

	json.Unmarshal(w.Body.Bytes(), &statusResp)
	assert.False(t, statusResp["limitations_enabled"].(bool),
		"Pro tier user with IsFree should see limitations_enabled=false")

	// ========== Test disabling IsFree restores normal behavior ==========
	sub.Tier = models.TierFree
	sub.IsFree = false
	err = app.SubscriptionRepo.Update(sub)
	require.NoError(t, err)

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

	json.Unmarshal(w.Body.Bytes(), &statusResp)
	assert.True(t, statusResp["limitations_enabled"].(bool),
		"Free tier user without IsFree should see limitations_enabled=true")
}

// TestIntegration_IsFreeWhenGlobalLimitationsDisabled tests that IsFree has no
// effect when global limitations are already disabled
func TestIntegration_IsFreeWhenGlobalLimitationsDisabled(t *testing.T) {
	app := setupSubscriptionTest(t)

	// Register and login a user
	token, userID := app.registerAndLogin(t, "globaluser", "global@test.com", "Password123")

	// Disable global limitations
	settings := &models.SubscriptionSettings{EnableLimitations: false}
	app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
	app.DB.Create(settings)

	// Get user's subscription
	sub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)

	// ========== Test 1: Without IsFree, limitations are disabled ==========
	sub.IsFree = false
	app.SubscriptionRepo.Update(sub)

	w := app.makeAuthenticatedRequest(t, "GET", "/api/subscription/status/", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var statusResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &statusResp)
	assert.False(t, statusResp["limitations_enabled"].(bool),
		"When global limitations are disabled, limitations_enabled should be false")

	// ========== Test 2: With IsFree, limitations are still disabled ==========
	sub.IsFree = true
	app.SubscriptionRepo.Update(sub)

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

	json.Unmarshal(w.Body.Bytes(), &statusResp)
	assert.False(t, statusResp["limitations_enabled"].(bool),
		"With IsFree and global limitations disabled, limitations_enabled should be false")

	// Both cases result in the same outcome - no limitations
	err = app.SubscriptionService.CheckLimit(userID, "properties")
	assert.NoError(t, err, "Should bypass limits when global limitations are disabled")
}
