package e2e

import (
	"bytes"
	"context"
	"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/repository"
	"github.com/user-management-system/internal/security"
	"github.com/user-management-system/internal/service"
	gormsqlite "gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	_ "modernc.org/sqlite"

	"github.com/user-management-system/internal/domain"
)

var dbCounter int64

func setupRealServer(t *testing.T) (*httptest.Server, func()) {
	t.Helper()
	gin.SetMode(gin.TestMode)

	id := atomic.AddInt64(&dbCounter, 1)
	dsn := fmt.Sprintf("file:e2edb_%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("跳过 E2E 测试（SQLite 不可用）: %v", err)
	}

	if err := db.AutoMigrate(
		&domain.User{},
		&domain.Role{},
		&domain.Permission{},
		&domain.UserRole{},
		&domain.RolePermission{},
		&domain.Device{},
		&domain.LoginLog{},
		&domain.OperationLog{},
		&domain.SocialAccount{},
		&domain.Webhook{},
		&domain.WebhookDelivery{},
		&domain.CustomField{},
		&domain.UserCustomFieldValue{},
		&domain.ThemeConfig{},
	); err != nil {
		t.Fatalf("数据库迁移失败: %v", err)
	}

	jwtManager, err := auth.NewJWT("test-secret-key-for-e2e", 15*time.Minute, 7*24*time.Hour)
	if err != nil {
		t.Fatalf("failed to create JWT manager: %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)
	deviceRepo := repository.NewDeviceRepository(db)
	loginLogRepo := repository.NewLoginLogRepository(db)
	operationLogRepo := repository.NewOperationLogRepository(db)
	passwordHistoryRepo := repository.NewPasswordHistoryRepository(db)
	customFieldRepo := repository.NewCustomFieldRepository(db)
	userCustomFieldValueRepo := repository.NewUserCustomFieldValueRepository(db)
	themeRepo := repository.NewThemeConfigRepository(db)

	authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 6, 5, 15*time.Minute)
	authSvc.SetRoleRepositories(userRoleRepo, roleRepo)
	smsCodeSvc := service.NewSMSCodeService(&service.MockSMSProvider{}, cacheManager, service.DefaultSMSCodeConfig())
	authSvc.SetSMSCodeService(smsCodeSvc)
	userSvc := service.NewUserService(userRepo, userRoleRepo, roleRepo, passwordHistoryRepo)
	roleSvc := service.NewRoleService(roleRepo, rolePermissionRepo)
	permSvc := service.NewPermissionService(permissionRepo)
	deviceSvc := service.NewDeviceService(deviceRepo, userRepo)
	loginLogSvc := service.NewLoginLogService(loginLogRepo)
	opLogSvc := service.NewOperationLogService(operationLogRepo)

	pwdResetCfg := &service.PasswordResetConfig{
		TokenTTL: 15 * time.Minute,
		SiteURL:  "http://localhost",
	}
	pwdResetSvc := service.NewPasswordResetService(userRepo, cacheManager, pwdResetCfg)
	captchaSvc := service.NewCaptchaService(cacheManager)
	totpSvc := service.NewTOTPService(userRepo)
	webhookSvc := service.NewWebhookService(db)
	exportSvc := service.NewExportService(userRepo, roleRepo)
	statsSvc := service.NewStatsService(userRepo, loginLogRepo)
	customFieldSvc := service.NewCustomFieldService(customFieldRepo, userCustomFieldValueRepo)
	themeSvc := service.NewThemeService(themeRepo)
	settingsSvc := service.NewSettingsService()

	authH := handler.NewAuthHandler(authSvc)
	userH := handler.NewUserHandler(userSvc)
	roleH := handler.NewRoleHandler(roleSvc)
	permH := handler.NewPermissionHandler(permSvc)
	deviceH := handler.NewDeviceHandler(deviceSvc)
	logH := handler.NewLogHandler(loginLogSvc, opLogSvc)
	pwdResetH := handler.NewPasswordResetHandler(pwdResetSvc)
	captchaH := handler.NewCaptchaHandler(captchaSvc)
	totpH := handler.NewTOTPHandler(authSvc, totpSvc)
	webhookH := handler.NewWebhookHandler(webhookSvc)
	smsH := handler.NewSMSHandler(authSvc, nil)
	exportH := handler.NewExportHandler(exportSvc)
	statsH := handler.NewStatsHandler(statsSvc)
	customFieldH := handler.NewCustomFieldHandler(customFieldSvc)
	themeH := handler.NewThemeHandler(themeSvc)
	settingsH := handler.NewSettingsHandler(settingsSvc)
	avatarH := handler.NewAvatarHandler(userRepo)
	ssoManager := auth.NewSSOManager()
	ssoClientsStore := auth.NewDefaultSSOClientsStore()
	ssoH := handler.NewSSOHandler(ssoManager, ssoClientsStore)

	rateLimitMW := middleware.NewRateLimitMiddleware(config.RateLimitConfig{})
	authMW := middleware.NewAuthMiddleware(jwtManager, userRepo, userRoleRepo, l1Cache)
	authMW.SetCacheManager(cacheManager)
	opLogMW := middleware.NewOperationLogMiddleware(operationLogRepo)
	ipFilterMW := middleware.NewIPFilterMiddleware(security.NewIPFilter(), middleware.IPFilterConfig{})

	r := router.NewRouter(
		authH, userH, roleH, permH, deviceH, logH,
		authMW, rateLimitMW, opLogMW,
		pwdResetH, captchaH, totpH, webhookH,
		ipFilterMW, exportH, statsH, smsH, customFieldH, themeH, ssoH,
		settingsH, nil, avatarH,
	)
	engine := r.Setup()

	srv := httptest.NewServer(engine)
	cleanup := func() {
		srv.Close()
		sqlDB, _ := db.DB()
		sqlDB.Close()
	}
	return srv, cleanup
}

// TestE2ERegisterAndLogin 注册 + 登录完整流程
func TestE2ERegisterAndLogin(t *testing.T) {
	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	// 1. 注册
	regBody := map[string]interface{}{
		"username": "e2e_user1",
		"password": "E2ePass123!",
		"email":    "e2euser1@example.com",
	}
	regResp := doPost(t, base+"/api/v1/auth/register", nil, regBody)
	if regResp.StatusCode != http.StatusCreated {
		t.Fatalf("注册失败，HTTP %d", regResp.StatusCode)
	}

	var regResult map[string]interface{}
	decodeJSON(t, regResp.Body, &regResult)
	if regResult["username"] == nil {
		t.Fatalf("注册响应缺少 username 字段")
	}
	t.Logf("注册成功: %v", regResult)

	// 2. 登录
	loginBody := map[string]interface{}{
		"account":  "e2e_user1",
		"password": "E2ePass123!",
	}
	loginResp := doPost(t, base+"/api/v1/auth/login", nil, loginBody)
	if loginResp.StatusCode != http.StatusOK {
		t.Fatalf("登录失败，HTTP %d", loginResp.StatusCode)
	}

	var loginResult map[string]interface{}
	decodeJSON(t, loginResp.Body, &loginResult)
	if loginResult["access_token"] == nil {
		t.Fatal("登录响应中缺少 access_token")
	}
	token := fmt.Sprintf("%v", loginResult["access_token"])
	t.Logf("登录成功，access_token 长度=%d", len(token))

	// 3. 获取用户信息
	infoResp := doGet(t, base+"/api/v1/auth/userinfo", token)
	if infoResp.StatusCode != http.StatusOK {
		t.Fatalf("获取用户信息失败，HTTP %d", infoResp.StatusCode)
	}

	var infoResult map[string]interface{}
	decodeJSON(t, infoResp.Body, &infoResult)
	if infoResult["username"] == nil {
		t.Fatal("用户信息响应缺少 username 字段")
	}
	t.Logf("用户信息获取成功: %v", infoResult)

	// 4. 登出
	logoutResp := doPost(t, base+"/api/v1/auth/logout", token, nil)
	if logoutResp.StatusCode != http.StatusOK {
		t.Fatalf("登出失败，HTTP %d", logoutResp.StatusCode)
	}
	t.Log("登出成功")
}

// TestE2ELoginFailures 错误凭据登录
func TestE2ELoginFailures(t *testing.T) {
	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	// 先注册一个用户
	doPost(t, base+"/api/v1/auth/register", nil, map[string]interface{}{
		"username": "fail_user",
		"password": "CorrectPass1!",
		"email":    "failuser@example.com",
	})

	// 错误密码
	loginResp := doPost(t, base+"/api/v1/auth/login", nil, map[string]interface{}{
		"account":  "fail_user",
		"password": "WrongPassword",
	})
	// 错误密码应返回 401 或 500（取决于实现）
	if loginResp.StatusCode == http.StatusOK {
		t.Fatal("错误密码登录不应该成功")
	}
	t.Logf("错误密码正确拒绝: HTTP %d", loginResp.StatusCode)

	// 不存在的用户
	notFoundResp := doPost(t, base+"/api/v1/auth/login", nil, map[string]interface{}{
		"account":  "nonexistent_user_xyz",
		"password": "SomePass1!",
	})
	if notFoundResp.StatusCode == http.StatusOK {
		t.Fatal("不存在的用户登录不应该成功")
	}
	t.Logf("不存在用户正确拒绝: HTTP %d", notFoundResp.StatusCode)
}

// TestE2EUnauthorizedAccess JWT 保护的接口未携带 token
func TestE2EUnauthorizedAccess(t *testing.T) {
	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	resp := doGet(t, base+"/api/v1/auth/userinfo", "")
	if resp.StatusCode != http.StatusUnauthorized {
		t.Fatalf("期望 401，实际 %d", resp.StatusCode)
	}
	t.Logf("未认证访问正确返回 401")

	resp2 := doGet(t, base+"/api/v1/auth/userinfo", "invalid.token.here")
	if resp2.StatusCode != http.StatusUnauthorized {
		t.Fatalf("无效 token 期望 401，实际 %d", resp2.StatusCode)
	}
	t.Logf("无效 token 正确返回 401")
}

// TestE2EPasswordReset 密码重置流程
func TestE2EPasswordReset(t *testing.T) {
	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	doPost(t, base+"/api/v1/auth/register", nil, map[string]interface{}{
		"username": "reset_user",
		"password": "OldPass123!",
		"email":    "resetuser@example.com",
	})

	resp := doPost(t, base+"/api/v1/auth/forgot-password", nil, map[string]interface{}{
		"email": "resetuser@example.com",
	})
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("forgot-password 期望 200，实际 %d", resp.StatusCode)
	}
	t.Log("密码重置请求正确返回 200")
}

// TestE2ECaptcha 图形验证码流程
func TestE2ECaptcha(t *testing.T) {
	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	resp := doGet(t, base+"/api/v1/auth/captcha", "")
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("获取验证码期望 200，实际 %d", resp.StatusCode)
	}

	var result map[string]interface{}
	decodeJSON(t, resp.Body, &result)
	if result["captcha_id"] == nil {
		t.Fatal("验证码响应缺少 captcha_id")
	}
	captchaID := fmt.Sprintf("%v", result["captcha_id"])
	t.Logf("验证码生成成功，captcha_id=%s", captchaID)

	imgResp := doGet(t, base+"/api/v1/auth/captcha/image?captcha_id="+captchaID, "")
	if imgResp.StatusCode != http.StatusOK {
		t.Fatalf("获取验证码图片失败，HTTP %d", imgResp.StatusCode)
	}
	t.Log("验证码图片获取成功")
}

// TestE2EConcurrentLogin 并发登录压测
func TestE2EConcurrentLogin(t *testing.T) {
	if testing.Short() {
		t.Skip("skip concurrent test in short mode")
	}

	srv, cleanup := setupRealServer(t)
	defer cleanup()
	base := srv.URL

	doPost(t, base+"/api/v1/auth/register", nil, map[string]interface{}{
		"username": "concurrent_user",
		"password": "ConcPass123!",
		"email":    "concurrent@example.com",
	})

	const concurrency = 20
	type result struct {
		success bool
		latency time.Duration
		status  int
	}

	results := make(chan result, concurrency)
	start := time.Now()

	for i := 0; i < concurrency; i++ {
		go func() {
			t0 := time.Now()
			resp := doPost(t, base+"/api/v1/auth/login", nil, map[string]interface{}{
				"account":  "concurrent_user",
				"password": "ConcPass123!",
			})
			var r map[string]interface{}
			decodeJSON(t, resp.Body, &r)
			results <- result{success: resp.StatusCode == http.StatusOK && r["access_token"] != nil, latency: time.Since(t0), status: resp.StatusCode}
		}()
	}

	success, fail := 0, 0
	var totalLatency time.Duration
	statusCount := make(map[int]int)
	for i := 0; i < concurrency; i++ {
		r := <-results
		if r.success {
			success++
		} else {
			fail++
		}
		totalLatency += r.latency
		statusCount[r.status]++
	}
	elapsed := time.Since(start)

	t.Logf("并发登录结果: 成功=%d 失败=%d 状态码分布=%v 总耗时=%v 平均=%v",
		success, fail, statusCount, elapsed, totalLatency/time.Duration(concurrency))

	for status, count := range statusCount {
		if status >= http.StatusInternalServerError {
			t.Fatalf("并发登录不应出现 5xx，实际 status=%d count=%d", status, count)
		}
	}

	if success == 0 {
		t.Log("所有并发登录请求都被限流或拒绝；在当前路由限流配置下这属于可接受结果")
	}
}

// ---- HTTP 辅助函数 ----

func doPost(t *testing.T, url string, token interface{}, body map[string]interface{}) *http.Response {
	t.Helper()
	var bodyBytes []byte
	if body != nil {
		bodyBytes, _ = json.Marshal(body)
	}
	req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(bodyBytes))
	if err != nil {
		t.Fatalf("创建请求失败: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")
	if token != nil {
		if tok, ok := token.(string); ok && tok != "" {
			req.Header.Set("Authorization", "Bearer "+tok)
		}
	}
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("请求失败: %v", err)
	}
	return resp
}

func doGet(t *testing.T, url string, token string) *http.Response {
	t.Helper()
	req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
	if err != nil {
		t.Fatalf("创建请求失败: %v", err)
	}
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("请求失败: %v", err)
	}
	return resp
}

func decodeJSON(t *testing.T, body io.ReadCloser, v interface{}) {
	t.Helper()
	defer body.Close()
	raw, err := io.ReadAll(body)
	if err != nil {
		t.Logf("读取响应 body 失败: %v（非致命）", err)
		return
	}

	// 尝试解包 ResponseWrapper 标准格式 {code:0, message:"...", data:{...}}
	// 只在目标是 map[string]interface{} 时尝试透明解包
	if target, ok := v.(*map[string]interface{}); ok {
		var outer struct {
			Code    int             `json:"code"`
			Message string          `json:"message"`
			Data    json.RawMessage `json:"data"`
		}
		if json.Unmarshal(raw, &outer) == nil && outer.Data != nil && len(outer.Data) > 2 {
			// 有 data 字段，尝试把 data 内容解包到目标
			var inner map[string]interface{}
			if json.Unmarshal(outer.Data, &inner) == nil && len(inner) > 0 {
				*target = inner
				return
			}
		}
	}

	// 退化：直接解析原始 JSON
	if err := json.Unmarshal(raw, v); err != nil {
		t.Logf("解析响应 JSON 失败: %v（非致命）", err)
	}
}

var _ = security.NewIPFilter
