package fingerprint

import (
	"crypto/sha256"
	"encoding/hex"
	"sort"
	"time"

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

const StoreVersion = "1.0.0"

type FunctionFingerprint = schema.FunctionFingerprint
type ClassFingerprint = schema.ClassFingerprint
type ImportFingerprint = schema.ImportFingerprint
type FileFingerprint = schema.FileFingerprint
type FingerprintStore = schema.FingerprintStore

func ContentHash(content []byte) string {
	sum := sha256.Sum256(content)
	return hex.EncodeToString(sum[:])
}

func BuildStore(analyses []extractor.StructuralAnalysis, gitCommitHash string, generatedAt time.Time, llmMode string) schema.FingerprintStore {
	if generatedAt.IsZero() {
		generatedAt = time.Now().UTC()
	}
	store := schema.FingerprintStore{
		Version:       StoreVersion,
		GitCommitHash: gitCommitHash,
		GeneratedAt:   generatedAt.UTC().Format(time.RFC3339),
		LLMMode:       llmMode,
		Files:         map[string]schema.FileFingerprint{},
	}
	for _, analysis := range analyses {
		fp := FromAnalysis(analysis)
		store.Files[fp.FilePath] = fp
	}
	return store
}

func FromAnalysis(analysis extractor.StructuralAnalysis) schema.FileFingerprint {
	fp := schema.FileFingerprint{
		FilePath:              slashPath(analysis.FilePath),
		ContentHash:           analysis.ContentHash,
		Functions:             make([]schema.FunctionFingerprint, 0, len(analysis.Functions)),
		Classes:               make([]schema.ClassFingerprint, 0, len(analysis.Types)),
		Imports:               make([]schema.ImportFingerprint, 0, len(analysis.Imports)),
		Exports:               sortedStrings(analysis.Exports),
		TotalLines:            analysis.TotalLines,
		HasStructuralAnalysis: analysis.HasStructuralAnalysis,
	}
	for _, fn := range analysis.Functions {
		fp.Functions = append(fp.Functions, schema.FunctionFingerprint{
			Name:       fn.Name,
			Params:     append([]string(nil), fn.Params...),
			ReturnType: fn.ReturnType,
			Exported:   fn.Exported,
			LineCount:  lineCount(fn.LineRange),
		})
	}
	for _, typ := range analysis.Types {
		fp.Classes = append(fp.Classes, schema.ClassFingerprint{
			Name:       typ.Name,
			Methods:    sortedStrings(typ.Methods),
			Properties: []string{},
			Exported:   typ.Exported,
			LineCount:  lineCount(typ.LineRange),
		})
	}
	for _, imp := range analysis.Imports {
		specifiers := sortedStrings(imp.Specifiers)
		if len(specifiers) == 0 && imp.Alias != "" {
			specifiers = []string{imp.Alias}
		}
		fp.Imports = append(fp.Imports, schema.ImportFingerprint{Source: imp.Path, Specifiers: specifiers})
	}
	SortFileFingerprint(&fp)
	return fp
}

func SortStore(store *schema.FingerprintStore) {
	normalized := map[string]schema.FileFingerprint{}
	for key, fp := range store.Files {
		fp.FilePath = firstNonEmpty(slashPath(fp.FilePath), slashPath(key))
		SortFileFingerprint(&fp)
		normalized[fp.FilePath] = fp
	}
	store.Files = normalized
}

func SortFileFingerprint(fp *schema.FileFingerprint) {
	if fp.Functions == nil {
		fp.Functions = []schema.FunctionFingerprint{}
	}
	if fp.Classes == nil {
		fp.Classes = []schema.ClassFingerprint{}
	}
	if fp.Imports == nil {
		fp.Imports = []schema.ImportFingerprint{}
	}
	if fp.Exports == nil {
		fp.Exports = []string{}
	}
	fp.FilePath = slashPath(fp.FilePath)
	sort.Slice(fp.Functions, func(i, j int) bool {
		if fp.Functions[i].Name != fp.Functions[j].Name {
			return fp.Functions[i].Name < fp.Functions[j].Name
		}
		if joinStrings(fp.Functions[i].Params) != joinStrings(fp.Functions[j].Params) {
			return joinStrings(fp.Functions[i].Params) < joinStrings(fp.Functions[j].Params)
		}
		return fp.Functions[i].ReturnType < fp.Functions[j].ReturnType
	})
	sort.Slice(fp.Classes, func(i, j int) bool { return fp.Classes[i].Name < fp.Classes[j].Name })
	for i := range fp.Functions {
		fp.Functions[i].Params = nonNilStrings(sortedStrings(fp.Functions[i].Params))
	}
	for i := range fp.Classes {
		fp.Classes[i].Methods = nonNilStrings(sortedStrings(fp.Classes[i].Methods))
		fp.Classes[i].Properties = nonNilStrings(sortedStrings(fp.Classes[i].Properties))
	}
	sort.Slice(fp.Imports, func(i, j int) bool {
		if fp.Imports[i].Source != fp.Imports[j].Source {
			return fp.Imports[i].Source < fp.Imports[j].Source
		}
		return joinStrings(fp.Imports[i].Specifiers) < joinStrings(fp.Imports[j].Specifiers)
	})
	for i := range fp.Imports {
		fp.Imports[i].Specifiers = nonNilStrings(sortedStrings(fp.Imports[i].Specifiers))
	}
	fp.Exports = nonNilStrings(sortedStrings(fp.Exports))
}

func nonNilStrings(values []string) []string {
	if values == nil {
		return []string{}
	}
	return values
}

func lineCount(lineRange [2]int) int {
	if lineRange[0] <= 0 || lineRange[1] < lineRange[0] {
		return 0
	}
	return lineRange[1] - lineRange[0] + 1
}
