package handlers

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

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/workers"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupTestWebhookHandler creates a test handler with real services and in-memory database
func setupTestWebhookHandler(t *testing.T) (*WebhookHandler, *gorm.DB, *gin.Engine, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate models
	err = db.AutoMigrate(
		&models.User{},
		&models.Webhook{},
	)
	require.NoError(t, err)

	// Setup webhook service
	jwtSecret := "test-secret-key"
	webhookService := services.NewWebhookService(db, logger, jwtSecret)

	// Setup webhook worker
	webhookWorker := workers.NewWebhookWorker(
		db,
		webhookService,
		logger,
		100, // Queue size
		5,   // Workers
		3,   // Max retries
	)

	handler := NewWebhookHandler(webhookService, webhookWorker, logger)

	router := gin.New()
	router.Use(func(c *gin.Context) {
		// Mock auth middleware - set user_id from header if present
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

	cleanup := func() {
		// Database cleanup handled by test
	}

	return handler, db, router, cleanup
}

// Helper to create a test user
func createTestUserForWebhook(id uuid.UUID, username string) *models.User {
	return &models.User{
		ID:         id,
		Username:   username,
		Email:      fmt.Sprintf("%s@example.com", username),
		IsActive:   true,
		IsVerified: true,
		CreatedAt:  time.Now(),
		UpdatedAt:  time.Now(),
	}
}

// TestWebhookHandler_RegisterWebhook_Success tests successful webhook registration
func TestWebhookHandler_RegisterWebhook_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/webhooks", handler.RegisterWebhook())

	registerReq := map[string]interface{}{
		"url":    "https://example.com/webhook",
		"events": []string{"track.created", "track.updated"},
	}
	body, _ := json.Marshal(registerReq)
	req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
}

// TestWebhookHandler_RegisterWebhook_InvalidURL tests webhook registration with invalid URL
func TestWebhookHandler_RegisterWebhook_InvalidURL(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/webhooks", handler.RegisterWebhook())

	registerReq := map[string]interface{}{
		"url":    "not-a-valid-url",
		"events": []string{"track.created"},
	}
	body, _ := json.Marshal(registerReq)
	req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestWebhookHandler_RegisterWebhook_NoEvents tests webhook registration with no events
func TestWebhookHandler_RegisterWebhook_NoEvents(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/webhooks", handler.RegisterWebhook())

	registerReq := map[string]interface{}{
		"url":    "https://example.com/webhook",
		"events": []string{},
	}
	body, _ := json.Marshal(registerReq)
	req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestWebhookHandler_RegisterWebhook_Unauthorized tests webhook registration without authentication
func TestWebhookHandler_RegisterWebhook_Unauthorized(t *testing.T) {
	handler, _, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	router.POST("/webhooks", handler.RegisterWebhook())

	registerReq := map[string]interface{}{
		"url":    "https://example.com/webhook",
		"events": []string{"track.created"},
	}
	body, _ := json.Marshal(registerReq)
	req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	// No X-User-ID header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

// TestWebhookHandler_ListWebhooks_Success tests successful webhook listing
func TestWebhookHandler_ListWebhooks_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test webhooks with unique API keys
	logger := zaptest.NewLogger(t)
	webhookService := services.NewWebhookService(db, logger, "test-secret")
	for i := 0; i < 3; i++ {
		apiKey, err := webhookService.GenerateAPIKey()
		require.NoError(t, err)
		webhook := &models.Webhook{
			ID:        uuid.New(),
			UserID:    userID,
			URL:       fmt.Sprintf("https://example.com/webhook%d", i+1),
			Events:    []string{"track.created"},
			Active:    true,
			APIKey:    apiKey,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		err = db.Create(webhook).Error
		require.NoError(t, err)
	}

	router.GET("/webhooks", handler.ListWebhooks())

	req := httptest.NewRequest(http.MethodGet, "/webhooks", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
}

// TestWebhookHandler_DeleteWebhook_Success tests successful webhook deletion
func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test webhook
	webhookID := uuid.New()
	webhook := &models.Webhook{
		ID:        webhookID,
		UserID:    userID,
		URL:       "https://example.com/webhook",
		Events:    []string{"track.created"},
		Active:    true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err = db.Create(webhook).Error
	require.NoError(t, err)

	router.DELETE("/webhooks/:id", handler.DeleteWebhook())

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/webhooks/%s", webhookID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
}

// TestWebhookHandler_DeleteWebhook_NotFound tests webhook deletion with non-existent webhook
func TestWebhookHandler_DeleteWebhook_NotFound(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.DELETE("/webhooks/:id", handler.DeleteWebhook())

	nonExistentID := uuid.New()
	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/webhooks/%s", nonExistentID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestWebhookHandler_DeleteWebhook_InvalidID tests webhook deletion with invalid ID format
func TestWebhookHandler_DeleteWebhook_InvalidID(t *testing.T) {
	handler, _, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	router.DELETE("/webhooks/:id", handler.DeleteWebhook())

	req := httptest.NewRequest(http.MethodDelete, "/webhooks/invalid-id", nil)
	req.Header.Set("X-User-ID", uuid.New().String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestWebhookHandler_GetWebhookStats_Success tests successful webhook stats retrieval
func TestWebhookHandler_GetWebhookStats_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.GET("/webhooks/stats", handler.GetWebhookStats())

	req := httptest.NewRequest(http.MethodGet, "/webhooks/stats", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
}

// TestWebhookHandler_TestWebhook_Success tests successful webhook test
func TestWebhookHandler_TestWebhook_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test webhook
	webhookID := uuid.New()
	webhook := &models.Webhook{
		ID:        webhookID,
		UserID:    userID,
		URL:       "https://example.com/webhook",
		Events:    []string{"track.created"},
		Active:    true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err = db.Create(webhook).Error
	require.NoError(t, err)

	router.POST("/webhooks/:id/test", handler.TestWebhook())

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/test", webhookID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
}

// TestWebhookHandler_TestWebhook_NotFound tests webhook test with non-existent webhook
func TestWebhookHandler_TestWebhook_NotFound(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/webhooks/:id/test", handler.TestWebhook())

	nonExistentID := uuid.New()
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/test", nonExistentID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestWebhookHandler_RegenerateAPIKey_Success tests successful API key regeneration
func TestWebhookHandler_RegenerateAPIKey_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test webhook
	webhookID := uuid.New()
	webhook := &models.Webhook{
		ID:        webhookID,
		UserID:    userID,
		URL:       "https://example.com/webhook",
		Events:    []string{"track.created"},
		Active:    true,
		APIKey:    "old_api_key",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err = db.Create(webhook).Error
	require.NoError(t, err)

	router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey())

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/regenerate-key", webhookID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)

	// Verify that API key is present in response
	dataBytes, _ := json.Marshal(response.Data)
	var data map[string]interface{}
	err = json.Unmarshal(dataBytes, &data)
	require.NoError(t, err)
	assert.Contains(t, data, "api_key")
	assert.NotEmpty(t, data["api_key"])
}

// TestWebhookHandler_RegenerateAPIKey_NotFound tests API key regeneration with non-existent webhook
func TestWebhookHandler_RegenerateAPIKey_NotFound(t *testing.T) {
	handler, db, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForWebhook(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey())

	nonExistentID := uuid.New()
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/regenerate-key", nonExistentID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestWebhookHandler_RegenerateAPIKey_InvalidID tests API key regeneration with invalid ID format
func TestWebhookHandler_RegenerateAPIKey_InvalidID(t *testing.T) {
	handler, _, router, cleanup := setupTestWebhookHandler(t)
	defer cleanup()

	router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey())

	req := httptest.NewRequest(http.MethodPost, "/webhooks/invalid-id/regenerate-key", nil)
	req.Header.Set("X-User-ID", uuid.New().String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}
