package semantic

import (
	"math"
	"path"
	"sort"
	"strings"

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

type rankedNode struct {
	ID     string `json:"id"`
	FanIn  int    `json:"fanIn"`
	FanOut int    `json:"fanOut"`
	Name   string `json:"name"`
}
type entryCandidate struct {
	ID      string `json:"id"`
	Score   int    `json:"score"`
	Name    string `json:"name"`
	Summary string `json:"summary"`
}
type bfsSignals struct {
	StartNode string              `json:"startNode"`
	Order     []string            `json:"order"`
	DepthMap  map[string]int      `json:"depthMap"`
	ByDepth   map[string][]string `json:"byDepth"`
}
type nonCodeNode struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Type    string `json:"type"`
	Summary string `json:"summary"`
}
type nonCodeSignals struct {
	Documentation  []nonCodeNode `json:"documentation"`
	Infrastructure []nonCodeNode `json:"infrastructure"`
	Data           []nonCodeNode `json:"data"`
	Config         []nonCodeNode `json:"config"`
}
type tourCluster struct {
	Nodes     []string `json:"nodes"`
	EdgeCount int      `json:"edgeCount"`
}
type tourLayerSignals struct {
	Count int         `json:"count"`
	List  []tourLayer `json:"list"`
}
type nodeSummary struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	Summary string `json:"summary"`
}
type TourSignals struct {
	ScriptCompleted      bool                   `json:"scriptCompleted"`
	EntryPointCandidates []entryCandidate       `json:"entryPointCandidates"`
	FanInRanking         []rankedNode           `json:"fanInRanking"`
	FanOutRanking        []rankedNode           `json:"fanOutRanking"`
	BFSTraversal         bfsSignals             `json:"bfsTraversal"`
	NonCodeFiles         nonCodeSignals         `json:"nonCodeFiles"`
	Clusters             []tourCluster          `json:"clusters"`
	Layers               tourLayerSignals       `json:"layers"`
	NodeSummaryIndex     map[string]nodeSummary `json:"nodeSummaryIndex"`
	TotalNodes           int                    `json:"totalNodes"`
	TotalEdges           int                    `json:"totalEdges"`
}

func BuildTourSignals(graph schema.KnowledgeGraph) TourSignals {
	nodes := fileLevelNodes(graph)
	valid := map[string]schema.GraphNode{}
	order := map[string]int{}
	for i, n := range nodes {
		valid[n.ID] = n
		order[n.ID] = i
	}
	fanIn, fanOut := map[string]int{}, map[string]int{}
	adj := map[string][]string{}
	relationships := map[string]map[string]map[string]bool{}
	validEdges := 0
	for _, e := range graph.Edges {
		if _, ok := valid[e.Source]; !ok {
			continue
		}
		if _, ok := valid[e.Target]; !ok {
			continue
		}
		validEdges++
		fanOut[e.Source]++
		fanIn[e.Target]++
		if e.Type == "imports" || e.Type == "calls" {
			adj[e.Source] = appendUnique(adj[e.Source], e.Target)
			if relationships[e.Source] == nil {
				relationships[e.Source] = map[string]map[string]bool{}
			}
			if relationships[e.Source][e.Target] == nil {
				relationships[e.Source][e.Target] = map[string]bool{}
			}
			relationships[e.Source][e.Target][e.Type] = true
		}
	}
	fanInRanking := ranking(nodes, fanIn, true)
	fanOutRanking := ranking(nodes, fanOut, false)
	allCandidates := entryCandidates(nodes, fanIn, fanOut, order)
	start := ""
	for _, c := range allCandidates {
		if valid[c.ID].Type == "file" {
			start = c.ID
			break
		}
	}
	candidates := allCandidates
	if len(candidates) > 5 {
		candidates = candidates[:5]
	}
	layers := make([]tourLayer, 0, len(graph.Layers))
	for _, layer := range graph.Layers {
		layers = append(layers, tourLayer{layer.ID, layer.Name, layer.Description})
	}
	index := map[string]nodeSummary{}
	nonCode := nonCodeSignals{Documentation: []nonCodeNode{}, Infrastructure: []nonCodeNode{}, Data: []nonCodeNode{}, Config: []nonCodeNode{}}
	for _, n := range nodes {
		index[n.ID] = nodeSummary{n.Name, n.Type, n.Summary}
		item := nonCodeNode{n.ID, n.Name, n.Type, n.Summary}
		switch n.Type {
		case "document":
			nonCode.Documentation = append(nonCode.Documentation, item)
		case "service", "pipeline", "resource":
			nonCode.Infrastructure = append(nonCode.Infrastructure, item)
		case "table", "schema", "endpoint":
			nonCode.Data = append(nonCode.Data, item)
		case "config":
			nonCode.Config = append(nonCode.Config, item)
		}
	}
	return TourSignals{true, candidates, fanInRanking, fanOutRanking, bfs(start, adj), nonCode, clusters(nodes, relationships, order), tourLayerSignals{len(layers), layers}, index, len(nodes), validEdges}
}
func ranking(nodes []schema.GraphNode, counts map[string]int, inbound bool) []rankedNode {
	out := make([]rankedNode, 0, len(nodes))
	for _, n := range nodes {
		r := rankedNode{ID: n.ID, Name: n.Name}
		if inbound {
			r.FanIn = counts[n.ID]
		} else {
			r.FanOut = counts[n.ID]
		}
		out = append(out, r)
	}
	sort.SliceStable(out, func(i, j int) bool { a, b := out[i].FanIn+out[i].FanOut, out[j].FanIn+out[j].FanOut; return a > b })
	if len(out) > 20 {
		out = out[:20]
	}
	return out
}
func entryCandidates(nodes []schema.GraphNode, fanIn, fanOut map[string]int, order map[string]int) []entryCandidate {
	fanValues := make([]int, 0, len(nodes))
	for _, n := range nodes {
		if n.Type == "file" {
			fanValues = append(fanValues, fanOut[n.ID])
		}
	}
	sort.Ints(fanValues)
	topThreshold := math.MaxInt
	if len(fanValues) > 0 {
		topThreshold = fanValues[max(0, int(math.Ceil(float64(len(fanValues))*.9))-1)]
	}
	inValues := make([]int, 0, len(nodes))
	for _, n := range nodes {
		if n.Type == "file" {
			inValues = append(inValues, fanIn[n.ID])
		}
	}
	sort.Ints(inValues)
	bottomThreshold := 0
	if len(inValues) > 0 {
		bottomThreshold = inValues[min(len(inValues)-1, int(math.Floor(float64(len(inValues))*.25)))]
	}
	out := []entryCandidate{}
	for _, n := range nodes {
		p := strings.Trim(strings.ReplaceAll(n.FilePath, `\`, "/"), "/")
		base := strings.ToLower(path.Base(p))
		score := 0
		if n.Type == "document" {
			if strings.EqualFold(p, "README.md") {
				score = 5
			} else if !strings.Contains(p, "/") && strings.HasSuffix(base, ".md") {
				score = 2
			}
		} else if n.Type == "file" {
			if isTourEntryFile(base) {
				score += 3
			}
			if strings.Count(p, "/") <= 1 {
				score++
			}
			if fanOut[n.ID] >= topThreshold {
				score++
			}
			if fanIn[n.ID] <= bottomThreshold {
				score++
			}
		}
		if score > 0 {
			out = append(out, entryCandidate{n.ID, score, n.Name, n.Summary})
		}
	}
	sort.SliceStable(out, func(i, j int) bool {
		if out[i].Score != out[j].Score {
			return out[i].Score > out[j].Score
		}
		return order[out[i].ID] < order[out[j].ID]
	})
	return out
}
func isTourEntryFile(base string) bool {
	for _, name := range []string{"index.ts", "index.js", "main.ts", "main.js", "app.ts", "app.js", "server.ts", "server.js", "mod.rs", "main.go", "main.py", "main.rs", "manage.py", "app.py", "wsgi.py", "asgi.py", "run.py", "__main__.py", "application.java", "main.java", "program.cs", "config.ru", "index.php", "app.swift", "application.kt", "main.cpp", "main.c"} {
		if base == name {
			return true
		}
	}
	return false
}
func bfs(start string, adj map[string][]string) bfsSignals {
	out := bfsSignals{StartNode: start, Order: []string{}, DepthMap: map[string]int{}, ByDepth: map[string][]string{}}
	if start == "" {
		return out
	}
	queue := []string{start}
	out.DepthMap[start] = 0
	for len(queue) > 0 {
		id := queue[0]
		queue = queue[1:]
		out.Order = append(out.Order, id)
		depth := out.DepthMap[id]
		key := itoa(depth)
		out.ByDepth[key] = append(out.ByDepth[key], id)
		for _, next := range adj[id] {
			if _, seen := out.DepthMap[next]; seen {
				continue
			}
			out.DepthMap[next] = depth + 1
			queue = append(queue, next)
		}
	}
	return out
}
func clusters(nodes []schema.GraphNode, rel map[string]map[string]map[string]bool, order map[string]int) []tourCluster {
	used := map[string]bool{}
	out := []tourCluster{}
	for _, a := range nodes {
		if used[a.ID] {
			continue
		}
		members := []string{a.ID}
		for _, b := range nodes {
			if a.ID == b.ID || used[b.ID] {
				continue
			}
			if sameTypeRelationship(rel, a.ID, b.ID) {
				members = append(members, b.ID)
				if len(members) == 5 {
					break
				}
			}
		}
		if len(members) < 2 {
			continue
		}
		changed := true
		for changed && len(members) < 5 {
			changed = false
			for _, candidate := range nodes {
				if contains(members, candidate.ID) || used[candidate.ID] {
					continue
				}
				connections := 0
				for _, m := range members {
					if connected(rel, candidate.ID, m) {
						connections++
					}
				}
				if connections >= 2 {
					members = append(members, candidate.ID)
					changed = true
					if len(members) == 5 {
						break
					}
				}
			}
		}
		edges := 0
		for _, aID := range members {
			for _, bID := range members {
				edges += len(rel[aID][bID])
			}
		}
		for _, id := range members {
			used[id] = true
		}
		out = append(out, tourCluster{members, edges})
	}
	sort.SliceStable(out, func(i, j int) bool {
		if out[i].EdgeCount != out[j].EdgeCount {
			return out[i].EdgeCount > out[j].EdgeCount
		}
		return order[out[i].Nodes[0]] < order[out[j].Nodes[0]]
	})
	if len(out) > 10 {
		out = out[:10]
	}
	return out
}

func sameTypeRelationship(rel map[string]map[string]map[string]bool, a, b string) bool {
	return rel[a][b]["imports"] && rel[b][a]["imports"] || rel[a][b]["calls"] && rel[b][a]["calls"]
}

func connected(rel map[string]map[string]map[string]bool, a, b string) bool {
	return len(rel[a][b]) > 0 || len(rel[b][a]) > 0
}
func appendUnique(values []string, value string) []string {
	for _, v := range values {
		if v == value {
			return values
		}
	}
	return append(values, value)
}
func contains(values []string, value string) bool {
	for _, v := range values {
		if v == value {
			return true
		}
	}
	return false
}
func itoa(value int) string {
	if value == 0 {
		return "0"
	}
	digits := ""
	for value > 0 {
		digits = string(rune('0'+value%10)) + digits
		value /= 10
	}
	return digits
}
