package handler_test

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

	"github.com/gin-gonic/gin"
	"github.com/user-management-system/internal/api/handler"
	"github.com/user-management-system/internal/api/middleware"
	"github.com/user-management-system/internal/api/router"
	"github.com/user-management-system/internal/auth"
	"github.com/user-management-system/internal/cache"
	"github.com/user-management-system/internal/config"
	"github.com/user-management-system/internal/domain"
	"github.com/user-management-system/internal/repository"
	"github.com/user-management-system/internal/service"
	gormsqlite "gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var webhookDbCounter int64

// doRequestWithCheck 执行HTTP请求并在失败时t.Fatalf
func doRequestWithCheck(t *testing.T, method, url string, token string, body interface{}) *http.Response {
	t.Helper()
	var bodyReader io.Reader
	if body != nil {
		jsonBytes, _ := json.Marshal(body)
		bodyReader = bytes.NewReader(jsonBytes)
	}
	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		t.Fatalf("create request failed: %v", err)
	}
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	return resp
}

func setupWebhookTestServer(t *testing.T) (*httptest.Server, *gorm.DB, string, func()) {
	t.Helper()
	gin.SetMode(gin.TestMode)

	id := atomic.AddInt64(&webhookDbCounter, 1)
	dsn := fmt.Sprintf("file:webhookdb_%d_%s?mode=memory&cache=shared", id, t.Name())
	db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
		DriverName: "sqlite",
		DSN:        dsn,
	}), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Skipf("skipping webhook handler test (SQLite unavailable): %v", err)
		return nil, nil, "", func() {}
	}

	if err := db.AutoMigrate(
		&domain.User{},
		&domain.Role{},
		&domain.Permission{},
		&domain.UserRole{},
		&domain.RolePermission{},
		&domain.Device{},
		&domain.LoginLog{},
		&domain.OperationLog{},
		&domain.PasswordHistory{},
		&domain.Webhook{},
		&domain.WebhookDelivery{},
	); err != nil {
		t.Fatalf("db migration failed: %v", err)
	}

	jwtManager, err := auth.NewJWTWithOptions(auth.JWTOptions{
		HS256Secret:        "test-webhook-secret-key",
		AccessTokenExpire:  15 * time.Minute,
		RefreshTokenExpire: 7 * 24 * time.Hour,
	})
	if err != nil {
		t.Fatalf("create jwt manager failed: %v", err)
	}

	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	userRepo := repository.NewUserRepository(db)
	roleRepo := repository.NewRoleRepository(db)
	permissionRepo := repository.NewPermissionRepository(db)
	userRoleRepo := repository.NewUserRoleRepository(db)
	rolePermissionRepo := repository.NewRolePermissionRepository(db)

	authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute)
	authSvc.SetRoleRepositories(userRoleRepo, roleRepo)

	webhookSvc := service.NewWebhookService(db)

	rateLimitCfg := config.RateLimitConfig{}
	rateLimitMiddleware := middleware.NewRateLimitMiddleware(rateLimitCfg)
	authMiddleware := middleware.NewAuthMiddleware(
		jwtManager, userRepo, userRoleRepo, roleRepo, rolePermissionRepo, permissionRepo, l1Cache,
	)
	authMiddleware.SetCacheManager(cacheManager)

	authHandler := handler.NewAuthHandler(authSvc)
	webhookHandler := handler.NewWebhookHandler(webhookSvc)

	r := router.NewRouter(
		authHandler, nil, nil, nil, nil, nil,
		authMiddleware, rateLimitMiddleware, nil,
		nil, nil, nil, webhookHandler,
		nil, nil, nil, nil, nil, nil, nil, nil, nil,
	)
	engine := r.Setup()
	server := httptest.NewServer(engine)

	// Register a user and get token
	registerReq := map[string]interface{}{
		"username": fmt.Sprintf("webhookuser_%d", time.Now().UnixNano()),
		"password": "TestPass123!",
		"email":    fmt.Sprintf("webhook_%d@test.com", time.Now().UnixNano()),
	}
	jsonBytes, _ := json.Marshal(registerReq)
	regResp, _ := http.Post(server.URL+"/api/v1/auth/register", "application/json", bytes.NewReader(jsonBytes))
	io.ReadAll(regResp.Body)
	regResp.Body.Close()

	// Login to get token
	loginReq := map[string]interface{}{
		"username": registerReq["username"],
		"password": registerReq["password"],
	}
	jsonBytes, _ = json.Marshal(loginReq)
	loginResp, _ := http.Post(server.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(jsonBytes))
	var loginResult struct {
		Data struct {
			AccessToken string `json:"access_token"`
		} `json:"data"`
	}
	json.NewDecoder(loginResp.Body).Decode(&loginResult)
	loginResp.Body.Close()
	token := loginResult.Data.AccessToken

	return server, db, token, func() {
		server.Close()
		if sqlDB, err := db.DB(); err == nil {
			sqlDB.Close()
		}
	}
}

func TestWebhookHandler_CreateWebhook_Success(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	reqBody := map[string]interface{}{
		"name":   "Test Webhook",
		"url":    "https://example.com/webhook",
		"events": []string{"user.created", "user.deleted"},
	}
	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("expected status 201, got %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	if result["code"].(float64) != 0 {
		t.Fatalf("expected code 0, got %v", result["code"])
	}
	if result["data"] == nil {
		t.Fatal("expected data in response")
	}
}

func TestWebhookHandler_CreateWebhook_InvalidURL(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	reqBody := map[string]interface{}{
		"name":   "Test Webhook",
		"url":    "not-a-valid-url",
		"events": []string{"user.created"},
	}

	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestWebhookHandler_CreateWebhook_MissingName(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	reqBody := map[string]interface{}{
		"url":    "https://example.com/webhook",
		"events": []string{"user.created"},
	}

	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestWebhookHandler_ListWebhooks_Success(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	// Create a webhook first
	reqBody := map[string]interface{}{
		"name":   "List Test Webhook",
		"url":    "https://example.com/webhook",
		"events": []string{"user.created"},
	}
	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
	resp.Body.Close()

	// List webhooks
	resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=10", token, nil)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("expected status 200, got %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	if result["code"].(float64) != 0 {
		t.Fatalf("expected code 0, got %v", result["code"])
	}
	data := result["data"].(map[string]interface{})
	if data["total"] == nil {
		t.Fatal("expected total in response")
	}
}

func TestWebhookHandler_UpdateWebhook_Success(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	// Create a webhook first
	createReq := map[string]interface{}{
		"name":   "Original Name",
		"url":    "https://example.com/webhook",
		"events": []string{"user.created"},
	}
	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
	var createResult map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResult)
	resp.Body.Close()

	webhookID := createResult["data"].(map[string]interface{})["id"].(float64)

	// Update the webhook
	updateReq := map[string]interface{}{
		"name": "Updated Name",
	}

	resp = doRequestWithCheck(t, "PUT", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), token, updateReq)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("expected status 200, got %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	if result["code"].(float64) != 0 {
		t.Fatalf("expected code 0, got %v", result["code"])
	}
}

func TestWebhookHandler_UpdateWebhook_InvalidID(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	updateReq := map[string]interface{}{
		"name": "Updated Name",
	}

	resp := doRequestWithCheck(t, "PUT", server.URL+"/api/v1/webhooks/invalid", token, updateReq)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	// Create a webhook first
	createReq := map[string]interface{}{
		"name":   "Delete Test Webhook",
		"url":    "https://example.com/webhook",
		"events": []string{"user.created"},
	}
	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
	var createResult map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResult)
	resp.Body.Close()

	webhookID := createResult["data"].(map[string]interface{})["id"].(float64)

	// Delete the webhook
	resp = doRequestWithCheck(t, "DELETE", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), token, nil)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("expected status 200, got %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	if result["code"].(float64) != 0 {
		t.Fatalf("expected code 0, got %v", result["code"])
	}
}

func TestWebhookHandler_DeleteWebhook_NotFound(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	resp := doRequestWithCheck(t, "DELETE", server.URL+"/api/v1/webhooks/99999", token, nil)
	defer resp.Body.Close()

	// Delete is idempotent - returns 200 even if not found
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestWebhookHandler_GetWebhookDeliveries_Success(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	// Create a webhook first
	createReq := map[string]interface{}{
		"name":   "Deliveries Test Webhook",
		"url":    "https://example.com/webhook",
		"events": []string{"user.created"},
	}
	resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
	var createResult map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResult)
	resp.Body.Close()

	webhookID := createResult["data"].(map[string]interface{})["id"].(float64)

	// Get webhook deliveries
	resp = doRequestWithCheck(t, "GET", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f/deliveries?limit=20", webhookID), token, nil)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("expected status 200, got %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	if result["code"].(float64) != 0 {
		t.Fatalf("expected code 0, got %v", result["code"])
	}
	if result["data"] == nil {
		t.Fatal("expected data in response")
	}
}

func TestWebhookHandler_GetWebhookDeliveries_InvalidID(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	resp := doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks/invalid/deliveries", token, nil)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected status 400, got %d", resp.StatusCode)
	}
}

func TestWebhookHandler_ListWebhooks_Pagination(t *testing.T) {
	server, _, token, cleanup := setupWebhookTestServer(t)
	defer cleanup()

	// Create multiple webhooks
	var resp *http.Response
	for i := 0; i < 3; i++ {
		reqBody := map[string]interface{}{
			"name":   fmt.Sprintf("Pagination Test Webhook %d", i),
			"url":    "https://example.com/webhook",
			"events": []string{"user.created"},
		}
		resp = doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
		resp.Body.Close()
	}

	// Test pagination
	resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=2", token, nil)
	defer resp.Body.Close()

	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)

	data := result["data"].(map[string]interface{})
	list := data["list"].([]interface{})
	if len(list) != 2 {
		t.Fatalf("expected 2 webhooks per page, got %d", len(list))
	}

	if data["page"].(float64) != 1 {
		t.Fatalf("expected page 1, got %v", data["page"])
	}
	if data["page_size"].(float64) != 2 {
		t.Fatalf("expected page_size 2, got %v", data["page_size"])
	}
}
