package integration

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

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	accountSvc "github.com/break/junhong_cmp_fiber/internal/service/account"
	accountAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/account_audit"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/response"
	"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// extractAccountID 从响应 data 中提取账号 ID
// gorm.Model 的 ID 字段在 JSON 中序列化为大写 "ID"
func extractAccountID(t *testing.T, data map[string]interface{}) uint {
	t.Helper()
	idVal := data["ID"]
	if idVal == nil {
		idVal = data["id"]
	}
	require.NotNil(t, idVal, "响应应包含 ID 字段")
	return uint(idVal.(float64))
}

// TestAccountAudit 账号操作审计日志集成测试
// 验证所有账号管理操作都被正确记录到审计日志
func TestAccountAudit(t *testing.T) {
	env := integ.NewIntegrationTestEnv(t)

	// 13.2 - 创建账号时记录审计日志
	t.Run("创建账号时记录审计日志", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)

		// 创建账号
		reqBody := dto.CreateAccountRequest{
			Username: fmt.Sprintf("test_%d", time.Now().UnixNano()),
			Phone:    fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
			Password: "Password123",
			UserType: constants.UserTypeAgent,
			ShopID:   &shop.ID,
		}

		jsonBody, err := json.Marshal(reqBody)
		require.NoError(t, err)

		resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
		require.NoError(t, err)
		defer resp.Body.Close()

		require.Equal(t, http.StatusOK, resp.StatusCode)

		// 解析响应获取账号 ID
		var result response.Response
		err = json.NewDecoder(resp.Body).Decode(&result)
		require.NoError(t, err)
		require.Equal(t, 0, result.Code, "创建账号应该成功，响应: %+v", result)
		require.NotNil(t, result.Data, "响应 data 不应为 nil")

		data, ok := result.Data.(map[string]interface{})
		require.True(t, ok, "响应 data 应为 map，实际: %T", result.Data)
		accountID := extractAccountID(t, data)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", accountID, "create").
			First(&log).Error
		require.NoError(t, err, "应该存在创建操作的审计日志")

		// 验证日志字段
		assert.Equal(t, "create", log.OperationType)
		assert.NotNil(t, log.AfterData, "创建操作应有 after_data")
		assert.Nil(t, log.BeforeData, "创建操作不应有 before_data")
		assert.NotNil(t, log.TargetUsername)
		assert.Equal(t, reqBody.Username, *log.TargetUsername)

		// 验证 after_data 包含账号信息
		afterData := log.AfterData
		assert.Equal(t, reqBody.Username, afterData["username"])
		assert.Equal(t, reqBody.Phone, afterData["phone"])
	})

	// 13.3 - 更新账号时记录 before_data 和 after_data
	t.Run("更新账号时记录before_data和after_data", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)
		account := env.CreateTestAccount("agent_update", "password123", constants.UserTypeAgent, &shop.ID, nil)

		// 记录原始数据
		originalUsername := account.Username

		// 更新账号
		newUsername := fmt.Sprintf("updated_%d", time.Now().UnixNano())
		reqBody := dto.UpdateAccountRequest{
			Username: &newUsername,
		}

		jsonBody, err := json.Marshal(reqBody)
		require.NoError(t, err)

		resp, err := env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/accounts/%d", account.ID), jsonBody)
		require.NoError(t, err)
		defer resp.Body.Close()

		require.Equal(t, http.StatusOK, resp.StatusCode)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "update").
			Order("created_at DESC").First(&log).Error
		require.NoError(t, err, "应该存在更新操作的审计日志")

		// 验证 before_data
		assert.NotNil(t, log.BeforeData, "更新操作应有 before_data")
		beforeData := log.BeforeData
		assert.Equal(t, originalUsername, beforeData["username"])

		// 验证 after_data
		assert.NotNil(t, log.AfterData, "更新操作应有 after_data")
		afterData := log.AfterData
		assert.Equal(t, newUsername, afterData["username"])
	})

	// 13.4 - 删除账号时记录审计日志
	t.Run("删除账号时记录审计日志", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)
		account := env.CreateTestAccount("agent_delete", "password123", constants.UserTypeAgent, &shop.ID, nil)

		// 删除账号
		resp, err := env.AsSuperAdmin().Request("DELETE", fmt.Sprintf("/api/admin/accounts/%d", account.ID), nil)
		require.NoError(t, err)
		defer resp.Body.Close()

		require.Equal(t, http.StatusOK, resp.StatusCode)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "delete").
			First(&log).Error
		require.NoError(t, err, "应该存在删除操作的审计日志")

		assert.Equal(t, "delete", log.OperationType)
		assert.NotNil(t, log.BeforeData, "删除操作应有 before_data")
		assert.Nil(t, log.AfterData, "删除操作不应有 after_data")
	})

	// 13.5 - 分配角色时记录审计日志
	t.Run("分配角色时记录审计日志", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)
		account := env.CreateTestAccount("agent_roles", "password123", constants.UserTypeAgent, &shop.ID, nil)
		// 代理账号使用 RoleTypeCustomer (2) 类型的角色
		role := env.CreateTestRole("测试角色", constants.RoleTypeCustomer)

		// 分配角色
		reqBody := dto.AssignRolesRequest{
			RoleIDs: []uint{role.ID},
		}
		jsonBody, err := json.Marshal(reqBody)
		require.NoError(t, err)

		resp, err := env.AsSuperAdmin().Request("POST", fmt.Sprintf("/api/admin/accounts/%d/roles", account.ID), jsonBody)
		require.NoError(t, err)
		defer resp.Body.Close()

		require.Equal(t, http.StatusOK, resp.StatusCode)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "assign_roles").
			First(&log).Error
		require.NoError(t, err, "应该存在分配角色操作的审计日志")

		assert.Equal(t, "assign_roles", log.OperationType)
		assert.NotNil(t, log.AfterData, "分配角色操作应有 after_data")
		afterData := log.AfterData
		roleIDs, ok := afterData["role_ids"].([]interface{})
		require.True(t, ok, "after_data 应包含 role_ids 数组")
		assert.Contains(t, roleIDs, float64(role.ID))
	})

	// 13.6 - 移除角色时记录审计日志
	// 由于路由参数名与 Handler 不匹配（路由用 :account_id，Handler 用 c.Params("id")），
	// 此测试创建独立的 AccountService 实例直接调用 RemoveRole 方法来验证审计日志
	t.Run("移除角色时记录审计日志", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)
		account := env.CreateTestAccount("agent_remove_role", "password123", constants.UserTypeAgent, &shop.ID, nil)
		role := env.CreateTestRole("测试角色", constants.RoleTypeCustomer)

		// 先分配角色
		assignReqBody := dto.AssignRolesRequest{
			RoleIDs: []uint{role.ID},
		}
		jsonBody, err := json.Marshal(assignReqBody)
		require.NoError(t, err)

		resp, err := env.AsSuperAdmin().Request("POST", fmt.Sprintf("/api/admin/accounts/%d/roles", account.ID), jsonBody)
		require.NoError(t, err)
		resp.Body.Close()
		require.Equal(t, http.StatusOK, resp.StatusCode)
		time.Sleep(200 * time.Millisecond)

		// 创建独立的 Service 实例来测试 RemoveRole
		accountStore := postgres.NewAccountStore(env.TX, env.Redis)
		roleStore := postgres.NewRoleStore(env.TX)
		accountRoleStore := postgres.NewAccountRoleStore(env.TX, env.Redis)
		shopStore := postgres.NewShopStore(env.TX, env.Redis)
		enterpriseStore := postgres.NewEnterpriseStore(env.TX, env.Redis)
		auditLogStore := postgres.NewAccountOperationLogStore(env.TX)
		auditService := accountAuditSvc.NewService(auditLogStore)
		accountService := accountSvc.New(accountStore, roleStore, accountRoleStore, shopStore, enterpriseStore, auditService)

		// 调用 RemoveRole
		ctx := env.GetSuperAdminContext()
		err = accountService.RemoveRole(ctx, account.ID, role.ID)
		require.NoError(t, err)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "remove_role").
			Order("created_at DESC").First(&log).Error
		require.NoError(t, err, "应该存在移除角色操作的审计日志")

		assert.Equal(t, "remove_role", log.OperationType)
		assert.NotNil(t, log.AfterData, "移除角色操作应有 after_data")
		afterData := log.AfterData
		assert.Equal(t, float64(role.ID), afterData["removed_role_id"])
	})

	// 13.7 - 审计日志包含完整的操作上下文
	t.Run("审计日志包含完整的操作上下文", func(t *testing.T) {
		shop := env.CreateTestShop("测试店铺", 1, nil)

		// 创建账号
		reqBody := dto.CreateAccountRequest{
			Username: fmt.Sprintf("test_ctx_%d", time.Now().UnixNano()),
			Phone:    fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
			Password: "Password123",
			UserType: constants.UserTypeAgent,
			ShopID:   &shop.ID,
		}

		jsonBody, err := json.Marshal(reqBody)
		require.NoError(t, err)

		resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
		require.NoError(t, err)
		defer resp.Body.Close()

		require.Equal(t, http.StatusOK, resp.StatusCode)

		// 解析响应
		var result response.Response
		err = json.NewDecoder(resp.Body).Decode(&result)
		require.NoError(t, err)

		data, ok := result.Data.(map[string]interface{})
		require.True(t, ok)
		accountID := extractAccountID(t, data)

		// 等待异步日志写入
		time.Sleep(200 * time.Millisecond)

		// 验证审计日志包含所有上下文
		var log model.AccountOperationLog
		err = env.RawDB().Where("target_account_id = ?", accountID).First(&log).Error
		require.NoError(t, err)

		// 验证操作人信息
		assert.NotZero(t, log.OperatorID, "应有操作人ID")
		assert.NotZero(t, log.OperatorType, "应有操作人类型")
		assert.NotEmpty(t, log.OperatorName, "应有操作人用户名")

		// 验证目标账号信息
		assert.NotNil(t, log.TargetAccountID, "应有目标账号ID")
		assert.Equal(t, accountID, *log.TargetAccountID)
		assert.NotNil(t, log.TargetUsername, "应有目标账号用户名")
		assert.NotNil(t, log.TargetUserType, "应有目标账号类型")

		// 验证请求上下文（集成测试中 RequestID/IP/UserAgent 可能为空，因为使用 httptest）
		// 但在真实环境中这些字段会被填充
	})

}

// TestAccountAudit_AsyncNotBlock 13.8 - 审计日志写入失败不影响业务操作
// 使用独立环境避免与其他测试的异步 goroutine 冲突
func TestAccountAudit_AsyncNotBlock(t *testing.T) {
	env := integ.NewIntegrationTestEnv(t)

	shop := env.CreateTestShop("测试店铺", 1, nil)

	reqBody := dto.CreateAccountRequest{
		Username: fmt.Sprintf("test_async_%d", time.Now().UnixNano()),
		Phone:    fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
		Password: "Password123",
		UserType: constants.UserTypeAgent,
		ShopID:   &shop.ID,
	}

	jsonBody, err := json.Marshal(reqBody)
	require.NoError(t, err)

	resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
	require.NoError(t, err)
	defer resp.Body.Close()

	require.Equal(t, http.StatusOK, resp.StatusCode)

	var result response.Response
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(t, err)
	assert.Equal(t, 0, result.Code, "业务操作应该成功，不受审计日志影响")
	assert.NotNil(t, result.Data, "应返回创建的账号数据")

	data, ok := result.Data.(map[string]interface{})
	require.True(t, ok)
	accountID := extractAccountID(t, data)

	time.Sleep(200 * time.Millisecond)

	var account model.Account
	err = env.RawDB().First(&account, accountID).Error
	require.NoError(t, err, "账号应该被成功创建到数据库")
	assert.Equal(t, reqBody.Username, account.Username)
}

// TestAccountAudit_OperationTypes 13.9 - 验证操作类型正确性
// 使用独立环境避免与其他测试的异步 goroutine 冲突
func TestAccountAudit_OperationTypes(t *testing.T) {
	env := integ.NewIntegrationTestEnv(t)

	shop := env.CreateTestShop("测试店铺", 1, nil)

	createReqBody := dto.CreateAccountRequest{
		Username: fmt.Sprintf("test_optype_%d", time.Now().UnixNano()),
		Phone:    fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
		Password: "Password123",
		UserType: constants.UserTypeAgent,
		ShopID:   &shop.ID,
	}
	jsonBody, err := json.Marshal(createReqBody)
	require.NoError(t, err)

	resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
	require.NoError(t, err)

	var result response.Response
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(t, err)
	resp.Body.Close()

	data, ok := result.Data.(map[string]interface{})
	require.True(t, ok)
	accountID := extractAccountID(t, data)
	time.Sleep(200 * time.Millisecond)

	newUsername := fmt.Sprintf("updated_optype_%d", time.Now().UnixNano())
	updateReqBody := dto.UpdateAccountRequest{
		Username: &newUsername,
	}
	jsonBody, err = json.Marshal(updateReqBody)
	require.NoError(t, err)

	resp, err = env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/accounts/%d", accountID), jsonBody)
	require.NoError(t, err)
	resp.Body.Close()
	time.Sleep(200 * time.Millisecond)

	var logs []model.AccountOperationLog
	err = env.RawDB().Where("target_account_id = ?", accountID).
		Order("created_at ASC").Find(&logs).Error
	require.NoError(t, err)

	require.GreaterOrEqual(t, len(logs), 2, "应该至少有 create 和 update 两条审计日志")

	operationTypes := make(map[string]bool)
	for _, log := range logs {
		operationTypes[log.OperationType] = true
	}

	assert.True(t, operationTypes["create"], "应该有 create 类型的审计日志")
	assert.True(t, operationTypes["update"], "应该有 update 类型的审计日志")
}
