package service

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	coremodel "security-center-backend/internal/model"
	vulnmodel "security-center-backend/internal/modules/vuln/model"
)

var (
	writeGuardBaseSubmittedAt = time.Date(2026, 7, 1, 10, 0, 0, 0, time.UTC)
	writeGuardBaseSLADeadline = writeGuardBaseSubmittedAt.Add(72 * time.Hour)
)

func writeGuardVuln(ownerUserID uint64) *vulnmodel.Vulnerability {
	return &vulnmodel.Vulnerability{
		Base:               coremodel.Base{ID: 77},
		PublicID:           "VULN-0077",
		Title:              "old title",
		Status:             vulnmodel.StatusActive,
		SeverityLevel:      vulnmodel.SeverityHigh,
		SubmitterUserID:    ownerUserID,
		HasPOCVerification: false,
		VerificationSteps:  "old steps",
		POCText:            "old poc",
		SubmittedAt:        writeGuardBaseSubmittedAt,
		SLADeadline:        writeGuardBaseSLADeadline,
	}
}

func writeGuardService(t *testing.T, ownerUserID uint64) (*Service, *accessLifecycleVulnRepo, *accessLifecycleEventRepo) {
	t.Helper()
	return newAccessLifecycleService(writeGuardVuln(ownerUserID), map[uint64]*coremodel.User{
		ownerUserID: userWithEmployeeAndPermissions(ownerUserID, 11, permissionEditDetails),
	})
}

func strPtr(s string) *string { return &s }

func boolPtr(b bool) *bool { return &b }

// ①决策 6（防盲改）：agent 变更三个敏感字段 → 整个请求 403，不落库、不记事件
func TestAgentUpdateSensitiveFieldChangeRejected(t *testing.T) {
	ownerUserID := uint64(10)
	cases := []struct {
		name  string
		field string
		input UpdateVulnerabilityInput
	}{
		{"poc_text", "poc_text", UpdateVulnerabilityInput{POCText: strPtr("new poc")}},
		{"verification_steps", "verification_steps", UpdateVulnerabilityInput{VerificationSteps: strPtr("new steps")}},
		{"has_poc_verification", "has_poc_verification", UpdateVulnerabilityInput{HasPOCVerification: boolPtr(true)}},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			svc, repo, events := writeGuardService(t, ownerUserID)
			ctx := agentClaimsContext(ownerUserID, permissionEditDetails)

			_, err := svc.UpdateVulnerability(ctx, 77, tc.input, "ci-bot", &ownerUserID)
			require.Error(t, err)
			require.Contains(t, err.Error(), "not authorized", "错误需映射为 403 语义")
			require.Contains(t, err.Error(), tc.field)
			require.Empty(t, repo.lastUpdateFields, "被拒请求不得产生任何字段写入")
			require.Empty(t, events.events, "被拒请求不得产生事件")
		})
	}
}

// ①决策 7（防 SLA 造假）：agent PUT 变更 submitted_at/sla_deadline → 403
func TestAgentUpdateTimeFieldChangeRejected(t *testing.T) {
	ownerUserID := uint64(10)
	later := writeGuardBaseSubmittedAt.Add(time.Hour)
	cases := []struct {
		name  string
		field string
		input UpdateVulnerabilityInput
	}{
		{"submitted_at", "submitted_at", UpdateVulnerabilityInput{SubmittedAt: &later}},
		{"sla_deadline", "sla_deadline", UpdateVulnerabilityInput{SLADeadline: &later}},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			svc, repo, events := writeGuardService(t, ownerUserID)
			ctx := agentClaimsContext(ownerUserID, permissionEditDetails)

			_, err := svc.UpdateVulnerability(ctx, 77, tc.input, "ci-bot", &ownerUserID)
			require.Error(t, err)
			require.Contains(t, err.Error(), "not authorized")
			require.Contains(t, err.Error(), tc.field)
			require.Empty(t, repo.lastUpdateFields)
			require.Empty(t, events.events)
		})
	}
}

// ①补充：请求体带同值不算"变更"，放行；agent 改普通字段不受影响
func TestAgentUpdateSameValueAndNormalFieldsAllowed(t *testing.T) {
	ownerUserID := uint64(10)
	svc, repo, _ := writeGuardService(t, ownerUserID)
	ctx := agentClaimsContext(ownerUserID, permissionEditDetails)

	sameSubmittedAt := writeGuardBaseSubmittedAt
	input := UpdateVulnerabilityInput{
		Title:              strPtr("new title"),
		POCText:            strPtr("old poc"), // 同值，不触发防盲改
		SubmittedAt:        &sameSubmittedAt,  // 同值，不触发 SLA 防护
		HasPOCVerification: boolPtr(false),    // 同值
	}
	vuln, err := svc.UpdateVulnerability(ctx, 77, input, "ci-bot", &ownerUserID)
	require.NoError(t, err)
	require.NotNil(t, vuln)
	require.Equal(t, "new title", repo.lastUpdateFields["title"])
	require.NotContains(t, repo.lastUpdateFields, "poc_text")
	require.NotContains(t, repo.lastUpdateFields, "submitted_at")
}

// ②决策 8 + 决策 7 Create 侧：submitter 强制为 Key 主人，时间字段强制服务端默认
func TestAgentCreateForcesOwnerSubmitterAndServerTimes(t *testing.T) {
	ownerUserID := uint64(10)
	svc, repo, _ := writeGuardService(t, ownerUserID)
	ctx := agentClaimsContext(ownerUserID, permissionEditDetails)

	past := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
	vuln := &vulnmodel.Vulnerability{
		Title:           "agent reported",
		SeverityLevel:   vulnmodel.SeverityHigh,
		Status:          vulnmodel.StatusActive,
		SubmitterUserID: 999, // 试图冒充他人
		SubmittedAt:     past,
		SLADeadline:     past,
	}
	before := time.Now().UTC()
	require.NoError(t, svc.Create(ctx, vuln))
	after := time.Now().UTC()

	created := repo.created
	require.NotNil(t, created)
	require.Equal(t, ownerUserID, created.SubmitterUserID, "submitter_user_id 必须被强制为 Key 主人")
	require.False(t, created.SubmittedAt.Before(before), "submitted_at 应为服务端当前时间")
	require.False(t, created.SubmittedAt.After(after))
	require.Equal(t, created.SubmittedAt.Add(72*time.Hour), created.SLADeadline,
		"high 级别 sla_deadline 应为 submitted_at + 72h 服务端默认")
}

// ③回归：人类（上下文无 agent claims）改敏感字段与时间字段不受影响
func TestHumanUpdateGuardedFieldsUnchanged(t *testing.T) {
	ownerUserID := uint64(10)
	svc, repo, _ := writeGuardService(t, ownerUserID)

	newSubmittedAt := writeGuardBaseSubmittedAt.Add(-24 * time.Hour)
	newSLA := writeGuardBaseSLADeadline.Add(48 * time.Hour)
	input := UpdateVulnerabilityInput{
		POCText:            strPtr("human new poc"),
		VerificationSteps:  strPtr("human new steps"),
		HasPOCVerification: boolPtr(true),
		SubmittedAt:        &newSubmittedAt,
		SLADeadline:        &newSLA,
	}
	vuln, err := svc.UpdateVulnerability(context.Background(), 77, input, "human@example.com", &ownerUserID)
	require.NoError(t, err)
	require.NotNil(t, vuln)
	require.Equal(t, "human new poc", repo.lastUpdateFields["poc_text"])
	require.Equal(t, "human new steps", repo.lastUpdateFields["verification_steps"])
	require.Equal(t, true, repo.lastUpdateFields["has_poc_verification"])
	require.Equal(t, newSubmittedAt, repo.lastUpdateFields["submitted_at"])
	require.Equal(t, newSLA, repo.lastUpdateFields["sla_deadline"])
}

// ③回归：人类 Create 可指定提交人与时间（补录场景），行为零变化
func TestHumanCreateKeepsRequestBodyValues(t *testing.T) {
	ownerUserID := uint64(10)
	svc, repo, _ := writeGuardService(t, ownerUserID)

	past := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
	customSLA := past.Add(30 * 24 * time.Hour)
	vuln := &vulnmodel.Vulnerability{
		Title:           "human backfill",
		SeverityLevel:   vulnmodel.SeverityHigh,
		Status:          vulnmodel.StatusActive,
		SubmitterUserID: 999,
		SubmittedAt:     past,
		SLADeadline:     customSLA,
	}
	require.NoError(t, svc.Create(context.Background(), vuln))

	created := repo.created
	require.NotNil(t, created)
	require.Equal(t, uint64(999), created.SubmitterUserID, "人类补录可指定任意提交人")
	require.Equal(t, past, created.SubmittedAt, "人类补录可指定 submitted_at")
	require.Equal(t, customSLA, created.SLADeadline, "人类补录可指定 sla_deadline")
}
