package fingerprint

import (
	"fmt"
	"path/filepath"
	"reflect"
	"sort"
	"strings"

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

type FileChangeKind string

const (
	ChangeNone       FileChangeKind = "NONE"
	ChangeCosmetic   FileChangeKind = "COSMETIC"
	ChangeStructural FileChangeKind = "STRUCTURAL"
)

type FileComparison struct {
	FilePath    string         `json:"filePath"`
	ChangeLevel FileChangeKind `json:"changeLevel"`
	Details     []string       `json:"details"`
	Added       bool           `json:"added,omitempty"`
	Deleted     bool           `json:"deleted,omitempty"`
}

func CompareFile(oldFile, newFile schema.FileFingerprint) FileComparison {
	path := firstNonEmpty(slashPath(newFile.FilePath), slashPath(oldFile.FilePath))
	oldFile.FilePath = slashPath(oldFile.FilePath)
	newFile.FilePath = slashPath(newFile.FilePath)
	SortFileFingerprint(&oldFile)
	SortFileFingerprint(&newFile)

	if oldFile.FilePath == "" && newFile.FilePath != "" {
		return FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{"file added"}, Added: true}
	}
	if oldFile.FilePath != "" && newFile.FilePath == "" {
		return FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{"file deleted"}, Deleted: true}
	}
	if oldFile.ContentHash != "" && oldFile.ContentHash == newFile.ContentHash {
		return FileComparison{FilePath: path, ChangeLevel: ChangeNone, Details: []string{}}
	}
	if !oldFile.HasStructuralAnalysis || !newFile.HasStructuralAnalysis {
		return FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{"no structural analysis available — conservative classification"}}
	}
	if reason := structuralDifference(oldFile, newFile); reason != "" {
		return FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{reason}}
	}
	return FileComparison{FilePath: path, ChangeLevel: ChangeCosmetic, Details: []string{"internal logic changed (no structural impact)"}}
}

func CompareStores(oldStore, newStore schema.FingerprintStore) []FileComparison {
	SortStore(&oldStore)
	SortStore(&newStore)
	paths := map[string]struct{}{}
	for path := range oldStore.Files {
		paths[slashPath(path)] = struct{}{}
	}
	for path := range newStore.Files {
		paths[slashPath(path)] = struct{}{}
	}
	ordered := make([]string, 0, len(paths))
	for path := range paths {
		ordered = append(ordered, path)
	}
	sort.Strings(ordered)
	comparisons := make([]FileComparison, 0, len(ordered))
	for _, path := range ordered {
		oldFile, oldOK := oldStore.Files[path]
		newFile, newOK := newStore.Files[path]
		switch {
		case !oldOK && newOK:
			newFile.FilePath = path
			comparisons = append(comparisons, FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{"file added"}, Added: true})
		case oldOK && !newOK:
			oldFile.FilePath = path
			comparisons = append(comparisons, FileComparison{FilePath: path, ChangeLevel: ChangeStructural, Details: []string{"file deleted"}, Deleted: true})
		default:
			comparisons = append(comparisons, CompareFile(oldFile, newFile))
		}
	}
	return comparisons
}

func structuralDifference(oldFile, newFile schema.FileFingerprint) string {
	if oldFile.FilePath != newFile.FilePath {
		return fmt.Sprintf("file path changed from %q to %q", oldFile.FilePath, newFile.FilePath)
	}
	if len(oldFile.Functions) != len(newFile.Functions) {
		return "function set changed"
	}
	for i := range oldFile.Functions {
		oldFn := oldFile.Functions[i]
		newFn := newFile.Functions[i]
		if oldFn.Name != newFn.Name || !reflect.DeepEqual(oldFn.Params, newFn.Params) || oldFn.ReturnType != newFn.ReturnType || oldFn.Exported != newFn.Exported {
			return fmt.Sprintf("function %q signature changed", firstNonEmpty(oldFn.Name, newFn.Name))
		}
		if !withinLineThreshold(oldFn.LineCount, newFn.LineCount) {
			return fmt.Sprintf("function %q line count changed from %d to %d", oldFn.Name, oldFn.LineCount, newFn.LineCount)
		}
	}
	if len(oldFile.Classes) != len(newFile.Classes) {
		return "class set changed"
	}
	for i := range oldFile.Classes {
		oldClass := oldFile.Classes[i]
		newClass := newFile.Classes[i]
		if oldClass.Name != newClass.Name || !reflect.DeepEqual(oldClass.Methods, newClass.Methods) || !reflect.DeepEqual(oldClass.Properties, newClass.Properties) || oldClass.Exported != newClass.Exported {
			return fmt.Sprintf("class %q signature changed", firstNonEmpty(oldClass.Name, newClass.Name))
		}
		if !withinLineThreshold(oldClass.LineCount, newClass.LineCount) {
			return fmt.Sprintf("class %q line count changed from %d to %d", oldClass.Name, oldClass.LineCount, newClass.LineCount)
		}
	}
	if !reflect.DeepEqual(oldFile.Imports, newFile.Imports) {
		return "import set changed"
	}
	if !reflect.DeepEqual(oldFile.Exports, newFile.Exports) {
		return "export set changed"
	}
	return ""
}

func withinLineThreshold(oldCount, newCount int) bool {
	if oldCount == newCount {
		return true
	}
	if oldCount <= 0 || newCount <= 0 {
		return oldCount == newCount
	}
	return newCount*2 >= oldCount && newCount <= oldCount+oldCount/2
}

func slashPath(path string) string {
	path = strings.TrimSpace(path)
	if path == "" {
		return ""
	}
	path = filepath.ToSlash(filepath.Clean(path))
	path = strings.TrimPrefix(path, "./")
	if path == "." {
		return ""
	}
	return path
}

func sortedStrings(values []string) []string {
	out := append([]string(nil), values...)
	sort.Strings(out)
	return out
}

func joinStrings(values []string) string { return strings.Join(values, "\x00") }

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
