package fingerprint

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

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

type UpdateAction string

const (
	ActionSkip               UpdateAction = "SKIP"
	ActionPartialUpdate      UpdateAction = "PARTIAL_UPDATE"
	ActionArchitectureUpdate UpdateAction = "ARCHITECTURE_UPDATE"
	ActionFullUpdate         UpdateAction = "FULL_UPDATE"
)

type ClassificationOptions struct {
	GraphFileCount            int
	DirectoryStructureChanged bool
}

type UpdateClassification struct {
	Action                    UpdateAction     `json:"action"`
	StructuralFiles           []string         `json:"structuralFiles,omitempty"`
	CosmeticFiles             []string         `json:"cosmeticFiles,omitempty"`
	UnchangedFiles            []string         `json:"unchangedFiles,omitempty"`
	DeletedFiles              []string         `json:"deletedFiles,omitempty"`
	AddedFiles                []string         `json:"addedFiles,omitempty"`
	FilesToReanalyze          []string         `json:"filesToReanalyze,omitempty"`
	DirectoryStructureChanged bool             `json:"directoryStructureChanged,omitempty"`
	RerunArchitecture         bool             `json:"rerunArchitecture"`
	RerunTour                 bool             `json:"rerunTour"`
	Reason                    string           `json:"reason"`
	Comparisons               []FileComparison `json:"comparisons,omitempty"`
}

func ClassifyStores(oldStore, newStore schema.FingerprintStore) UpdateClassification {
	comparisons := CompareStores(oldStore, newStore)
	return ClassifyChanges(comparisons, ClassificationOptions{
		GraphFileCount:            len(oldStore.Files),
		DirectoryStructureChanged: topLevelDirsChanged(oldStore.Files, newStore.Files),
	})
}

func ClassifyChanges(comparisons []FileComparison, options ClassificationOptions) UpdateClassification {
	result := UpdateClassification{Action: ActionSkip, Reason: "no structural changes", Comparisons: append([]FileComparison(nil), comparisons...), DirectoryStructureChanged: options.DirectoryStructureChanged}
	for _, comparison := range comparisons {
		path := slashPath(comparison.FilePath)
		switch comparison.ChangeLevel {
		case ChangeStructural:
			result.StructuralFiles = append(result.StructuralFiles, path)
			if comparison.Added {
				result.AddedFiles = append(result.AddedFiles, path)
			}
			if comparison.Deleted {
				result.DeletedFiles = append(result.DeletedFiles, path)
			} else {
				result.FilesToReanalyze = append(result.FilesToReanalyze, path)
			}
		case ChangeCosmetic:
			result.CosmeticFiles = append(result.CosmeticFiles, path)
		case ChangeNone:
			result.UnchangedFiles = append(result.UnchangedFiles, path)
		}
	}
	sort.Strings(result.StructuralFiles)
	sort.Strings(result.CosmeticFiles)
	sort.Strings(result.UnchangedFiles)
	sort.Strings(result.DeletedFiles)
	sort.Strings(result.AddedFiles)
	sort.Strings(result.FilesToReanalyze)

	structuralCount := len(result.StructuralFiles)
	graphFileCount := options.GraphFileCount
	if graphFileCount <= 0 {
		graphFileCount = len(comparisons)
	}
	if structuralCount == 0 {
		return result
	}
	if structuralCount > 30 {
		result.Action = ActionFullUpdate
		result.RerunArchitecture = true
		result.RerunTour = true
		result.Reason = "more than 30 structural files changed"
		return result
	}
	if graphFileCount > 0 && structuralCount*2 > graphFileCount {
		result.Action = ActionFullUpdate
		result.RerunArchitecture = true
		result.RerunTour = true
		result.Reason = "more than 50% of graph files changed structurally"
		return result
	}
	if options.DirectoryStructureChanged {
		result.Action = ActionArchitectureUpdate
		result.RerunArchitecture = true
		result.RerunTour = true
		result.Reason = "top-level directory structure changed"
		return result
	}
	if structuralCount > 10 {
		result.Action = ActionArchitectureUpdate
		result.RerunArchitecture = true
		result.RerunTour = true
		result.Reason = "more than 10 structural files changed"
		return result
	}
	result.Action = ActionPartialUpdate
	result.Reason = "localized structural changes"
	return result
}

func topLevelDirsChanged(oldFiles, newFiles map[string]schema.FileFingerprint) bool {
	return !sameStringSet(topLevelDirs(oldFiles), topLevelDirs(newFiles))
}

func topLevelDirs(files map[string]schema.FileFingerprint) map[string]struct{} {
	set := map[string]struct{}{}
	for path := range files {
		path = slashPath(path)
		if path == "" {
			continue
		}
		first := path
		if idx := strings.IndexByte(path, '/'); idx >= 0 {
			first = path[:idx]
		} else if ext := filepath.Ext(path); ext != "" {
			first = "."
		}
		set[first] = struct{}{}
	}
	return set
}

func sameStringSet(left, right map[string]struct{}) bool {
	if len(left) != len(right) {
		return false
	}
	for key := range left {
		if _, ok := right[key]; !ok {
			return false
		}
	}
	return true
}
