package fingerprint

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/kits/agentteam-insight/internal/graphbuilder/extractor"
	"github.com/tidbcloud/AgentTeam/kits/agentteam-insight/internal/graphbuilder/schema"
)

func TestFromAnalysisMatchesFingerprintContract(t *testing.T) {
	analysis := extractor.StructuralAnalysis{
		FilePath:              "./pkg/service/service.go",
		ContentHash:           ContentHash([]byte("package service\n")),
		TotalLines:            20,
		HasStructuralAnalysis: true,
		Functions: []extractor.Function{
			{Name: "run", Params: []string{"int"}, ReturnType: "error", Exported: false, LineRange: [2]int{10, 15}},
			{Name: "Run", Params: []string{"string"}, ReturnType: "", Exported: true, LineRange: [2]int{1, 4}},
		},
		Types:   []extractor.Type{{Name: "Service", Kind: "struct", Exported: true, LineRange: [2]int{6, 8}, Methods: []string{"Stop", "Start"}}},
		Imports: []extractor.Import{{Path: "fmt"}, {Path: "example.com/acme/pkg/api", Alias: "api"}},
		Exports: []string{"Service", "Run"},
	}

	fp := FromAnalysis(analysis)
	require.Equal(t, "pkg/service/service.go", fp.FilePath)
	require.Equal(t, ContentHash([]byte("package service\n")), fp.ContentHash)
	require.Equal(t, []schema.FunctionFingerprint{
		{Name: "Run", Params: []string{"string"}, Exported: true, LineCount: 4},
		{Name: "run", Params: []string{"int"}, ReturnType: "error", Exported: false, LineCount: 6},
	}, fp.Functions)
	require.Equal(t, []schema.ClassFingerprint{{Name: "Service", Methods: []string{"Start", "Stop"}, Properties: []string{}, Exported: true, LineCount: 3}}, fp.Classes)
	require.Equal(t, []schema.ImportFingerprint{{Source: "example.com/acme/pkg/api", Specifiers: []string{"api"}}, {Source: "fmt", Specifiers: []string{}}}, fp.Imports)
	require.Equal(t, []string{"Run", "Service"}, fp.Exports)
	require.NoError(t, schema.ValidateFingerprintStore(BuildStore([]extractor.StructuralAnalysis{analysis}, "abc123", time.Date(2026, 7, 2, 0, 0, 0, 0, time.UTC), "auto")))
}

func TestCompareFileClassifiesNoneCosmeticStructuralAndConservativeFallback(t *testing.T) {
	base := fileFP("src/app.go", "old", true)

	sameHash := base
	require.Equal(t, ChangeNone, CompareFile(base, sameHash).ChangeLevel)

	cosmetic := base
	cosmetic.ContentHash = "new"
	cosmetic.TotalLines = 14
	cosmetic.Functions[0].LineCount = 5
	require.Equal(t, ChangeCosmetic, CompareFile(base, cosmetic).ChangeLevel)

	lineCountStructural := cloneFileFP(base)
	lineCountStructural.ContentHash = "new"
	lineCountStructural.Functions[0].LineCount = 9
	comparison := CompareFile(base, lineCountStructural)
	require.Equal(t, ChangeStructural, comparison.ChangeLevel)
	require.Contains(t, comparison.Details[0], "line count")

	missingAnalysis := cloneFileFP(base)
	missingAnalysis.ContentHash = "new"
	missingAnalysis.HasStructuralAnalysis = false
	comparison = CompareFile(base, missingAnalysis)
	require.Equal(t, ChangeStructural, comparison.ChangeLevel)
	require.Contains(t, comparison.Details[0], "structural analysis")

	added := CompareFile(schema.FileFingerprint{}, base)
	require.Equal(t, ChangeStructural, added.ChangeLevel)
	require.True(t, added.Added)

	deleted := CompareFile(base, schema.FileFingerprint{})
	require.Equal(t, ChangeStructural, deleted.ChangeLevel)
	require.True(t, deleted.Deleted)
}

func TestClassifyUpdateActions(t *testing.T) {
	comparisons := []FileComparison{{FilePath: "src/app.go", ChangeLevel: ChangeCosmetic}}
	classification := ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 20})
	require.Equal(t, ActionSkip, classification.Action)
	require.Equal(t, []string{"src/app.go"}, classification.CosmeticFiles)

	comparisons = structuralComparisons(3)
	classification = ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 20})
	require.Equal(t, ActionPartialUpdate, classification.Action)
	require.False(t, classification.RerunArchitecture)
	require.Equal(t, []string{"src/file00.go", "src/file01.go", "src/file02.go"}, classification.FilesToReanalyze)

	classification = ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 20, DirectoryStructureChanged: true})
	require.Equal(t, ActionArchitectureUpdate, classification.Action)
	require.True(t, classification.RerunArchitecture)
	require.True(t, classification.RerunTour)
	require.Contains(t, classification.Reason, "top-level")

	comparisons = structuralComparisons(11)
	classification = ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 40})
	require.Equal(t, ActionArchitectureUpdate, classification.Action)

	comparisons = structuralComparisons(31)
	classification = ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 100})
	require.Equal(t, ActionFullUpdate, classification.Action)
	require.True(t, classification.RerunArchitecture)

	comparisons = structuralComparisons(6)
	classification = ClassifyChanges(comparisons, ClassificationOptions{GraphFileCount: 10})
	require.Equal(t, ActionFullUpdate, classification.Action)
}

func TestClassifyStoresDetectsTopLevelDirectoryChanges(t *testing.T) {
	oldStore := schema.FingerprintStore{Files: map[string]schema.FileFingerprint{"cmd/app.go": fileFP("cmd/app.go", "old", true)}}
	newStore := schema.FingerprintStore{Files: map[string]schema.FileFingerprint{"pkg/app.go": fileFP("pkg/app.go", "new", true)}}
	for i := 0; i < 18; i++ {
		path := fmt.Sprintf("shared/file%02d.go", i)
		oldStore.Files[path] = fileFP(path, "same", true)
		newStore.Files[path] = fileFP(path, "same", true)
	}
	classification := ClassifyStores(oldStore, newStore)
	require.Equal(t, ActionArchitectureUpdate, classification.Action)
	require.ElementsMatch(t, []string{"cmd/app.go", "pkg/app.go"}, classification.StructuralFiles)
}

func fileFP(path string, hash string, hasStructural bool) schema.FileFingerprint {
	return schema.FileFingerprint{
		FilePath:              path,
		ContentHash:           hash,
		Functions:             []schema.FunctionFingerprint{{Name: "Run", Params: []string{"string"}, ReturnType: "error", Exported: true, LineCount: 5}},
		Classes:               []schema.ClassFingerprint{{Name: "App", Methods: []string{"Run"}, Properties: []string{}, Exported: true, LineCount: 4}},
		Imports:               []schema.ImportFingerprint{{Source: "fmt", Specifiers: []string{}}},
		Exports:               []string{"App", "Run"},
		TotalLines:            12,
		HasStructuralAnalysis: hasStructural,
	}
}

func cloneFileFP(fp schema.FileFingerprint) schema.FileFingerprint {
	fp.Functions = append([]schema.FunctionFingerprint(nil), fp.Functions...)
	fp.Classes = append([]schema.ClassFingerprint(nil), fp.Classes...)
	fp.Imports = append([]schema.ImportFingerprint(nil), fp.Imports...)
	fp.Exports = append([]string(nil), fp.Exports...)
	return fp
}

func structuralComparisons(count int) []FileComparison {
	comparisons := make([]FileComparison, 0, count)
	for i := 0; i < count; i++ {
		comparisons = append(comparisons, FileComparison{FilePath: fmt.Sprintf("src/file%02d.go", i), ChangeLevel: ChangeStructural})
	}
	return comparisons
}
