package semantic

import (
	"path"
	"sort"
	"strings"

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

type groupImport struct {
	From  string `json:"from"`
	To    string `json:"to"`
	Count int    `json:"count"`
}

type crossCategoryEdge struct {
	FromType string `json:"fromType"`
	ToType   string `json:"toType"`
	EdgeType string `json:"edgeType"`
	Count    int    `json:"count"`
}

type groupDensity struct {
	InternalEdges int     `json:"internalEdges"`
	TotalEdges    int     `json:"totalEdges"`
	Density       float64 `json:"density"`
}

type dependencyDirection struct {
	Dependent string `json:"dependent"`
	DependsOn string `json:"dependsOn"`
}

type deploymentTopology struct {
	HasDockerfile bool     `json:"hasDockerfile"`
	HasCompose    bool     `json:"hasCompose"`
	HasK8s        bool     `json:"hasK8s"`
	HasTerraform  bool     `json:"hasTerraform"`
	HasCI         bool     `json:"hasCI"`
	InfraFiles    []string `json:"infraFiles"`
}

type dataPipeline struct {
	SchemaFiles     []string `json:"schemaFiles"`
	MigrationFiles  []string `json:"migrationFiles"`
	DataModelFiles  []string `json:"dataModelFiles"`
	APIHandlerFiles []string `json:"apiHandlerFiles"`
}

type docCoverage struct {
	GroupsWithDocs     int      `json:"groupsWithDocs"`
	TotalGroups        int      `json:"totalGroups"`
	CoverageRatio      float64  `json:"coverageRatio"`
	UndocumentedGroups []string `json:"undocumentedGroups"`
}

type architectureStats struct {
	TotalFileNodes int            `json:"totalFileNodes"`
	FilesPerGroup  map[string]int `json:"filesPerGroup"`
	NodeTypeCounts map[string]int `json:"nodeTypeCounts"`
}

type ArchitectureSignals struct {
	ScriptCompleted     bool                    `json:"scriptCompleted"`
	DirectoryGroups     map[string][]string     `json:"directoryGroups"`
	NodeTypeGroups      map[string][]string     `json:"nodeTypeGroups"`
	CrossCategoryEdges  []crossCategoryEdge     `json:"crossCategoryEdges"`
	InterGroupImports   []groupImport           `json:"interGroupImports"`
	IntraGroupDensity   map[string]groupDensity `json:"intraGroupDensity"`
	PatternMatches      map[string]string       `json:"patternMatches"`
	DeploymentTopology  deploymentTopology      `json:"deploymentTopology"`
	DataPipeline        dataPipeline            `json:"dataPipeline"`
	DocCoverage         docCoverage             `json:"docCoverage"`
	DependencyDirection []dependencyDirection   `json:"dependencyDirection"`
	FileStats           architectureStats       `json:"fileStats"`
	FileFanIn           map[string]int          `json:"fileFanIn"`
	FileFanOut          map[string]int          `json:"fileFanOut"`
}

func BuildArchitectureSignals(graph schema.KnowledgeGraph) ArchitectureSignals {
	nodes := fileLevelNodes(graph)
	valid := make(map[string]schema.GraphNode, len(nodes))
	for _, node := range nodes {
		valid[node.ID] = node
	}
	groups := directoryGroups(nodes)
	nodeGroup := map[string]string{}
	for group, ids := range groups {
		for _, id := range ids {
			nodeGroup[id] = group
		}
	}
	types := map[string][]string{}
	stats := architectureStats{TotalFileNodes: len(nodes), FilesPerGroup: map[string]int{}, NodeTypeCounts: map[string]int{}}
	for _, node := range nodes {
		types[node.Type] = append(types[node.Type], node.ID)
		stats.NodeTypeCounts[node.Type]++
		stats.FilesPerGroup[nodeGroup[node.ID]]++
	}

	fanIn, fanOut := map[string]int{}, map[string]int{}
	for _, node := range nodes {
		fanIn[node.ID] = 0
		fanOut[node.ID] = 0
	}
	inter := map[string]int{}
	cross := map[string]int{}
	internal, total := map[string]int{}, map[string]int{}
	for _, edge := range graph.Edges {
		source, sourceOK := valid[edge.Source]
		target, targetOK := valid[edge.Target]
		if !sourceOK || !targetOK {
			continue
		}
		if edge.Type == "imports" {
			fanOut[edge.Source]++
			fanIn[edge.Target]++
			from, to := nodeGroup[edge.Source], nodeGroup[edge.Target]
			if from == to {
				internal[from]++
			} else {
				inter[from+"\x00"+to]++
			}
			if from == to {
				total[from]++
			} else {
				total[from]++
				total[to]++
			}
		}
		if source.Type != target.Type {
			cross[source.Type+"\x00"+target.Type+"\x00"+edge.Type]++
		}
	}
	interList := make([]groupImport, 0, len(inter))
	for key, count := range inter {
		parts := strings.Split(key, "\x00")
		interList = append(interList, groupImport{parts[0], parts[1], count})
	}
	sort.Slice(interList, func(i, j int) bool {
		if interList[i].From != interList[j].From {
			return interList[i].From < interList[j].From
		}
		return interList[i].To < interList[j].To
	})
	crossList := make([]crossCategoryEdge, 0, len(cross))
	for key, count := range cross {
		parts := strings.Split(key, "\x00")
		crossList = append(crossList, crossCategoryEdge{parts[0], parts[1], parts[2], count})
	}
	sort.Slice(crossList, func(i, j int) bool {
		a, b := crossList[i], crossList[j]
		if a.FromType != b.FromType {
			return a.FromType < b.FromType
		}
		if a.ToType != b.ToType {
			return a.ToType < b.ToType
		}
		return a.EdgeType < b.EdgeType
	})
	densities := map[string]groupDensity{}
	patterns := map[string]string{}
	for group := range groups {
		density := 0.0
		if total[group] > 0 {
			density = float64(internal[group]) / float64(total[group])
		}
		densities[group] = groupDensity{internal[group], total[group], density}
		if label := architecturePattern(group, nodes, groups[group]); label != "" {
			patterns[group] = label
		}
	}

	return ArchitectureSignals{true, groups, types, crossList, interList, densities, patterns, deploymentSignals(nodes), dataSignals(nodes), documentationSignals(nodes, graph.Edges, groups, nodeGroup), dependencySignals(inter), stats, fanIn, fanOut}
}

func directoryGroups(nodes []schema.GraphNode) map[string][]string {
	paths := make([][]string, len(nodes))
	common := []string(nil)
	for i, node := range nodes {
		clean := strings.Trim(strings.ReplaceAll(node.FilePath, `\`, "/"), "/")
		dir := path.Dir(clean)
		if dir == "." {
			dir = ""
		}
		if dir != "" {
			paths[i] = strings.Split(dir, "/")
		}
		if i == 0 {
			common = append([]string(nil), paths[i]...)
		} else {
			common = commonPrefix(common, paths[i])
		}
	}
	flat := true
	for _, parts := range paths {
		if len(parts) > len(common) {
			flat = false
			break
		}
	}
	out := map[string][]string{}
	for i, node := range nodes {
		group := "root"
		if !flat && len(paths[i]) > len(common) {
			group = paths[i][len(common)]
		} else if flat {
			group = flatFileGroup(node.FilePath)
		}
		out[group] = append(out[group], node.ID)
	}
	return out
}
func commonPrefix(a, b []string) []string {
	n := min(len(a), len(b))
	i := 0
	for i < n && a[i] == b[i] {
		i++
	}
	return a[:i]
}
func flatFileGroup(file string) string {
	lower := strings.ToLower(path.Base(strings.ReplaceAll(file, `\`, "/")))
	switch {
	case isTestFile(lower):
		return "test"
	case strings.Contains(lower, ".config.") || strings.HasSuffix(lower, ".config"):
		return "config"
	default:
		ext := strings.TrimPrefix(path.Ext(lower), ".")
		if ext != "" {
			return ext
		}
		return "root"
	}
}

var directoryPatterns = []struct {
	label  string
	values []string
}{
	{"api", []string{"routes", "api", "controllers", "endpoints", "handlers", "controller", "routers", "serializers", "blueprints"}},
	{"service", []string{"services", "core", "lib", "domain", "logic", "signals", "internal", "composables", "mailers", "jobs", "channels"}},
	{"data", []string{"models", "db", "data", "persistence", "repository", "entities", "migrations", "entity", "sql", "database", "schema"}},
	{"ui", []string{"components", "views", "pages", "ui", "layouts", "screens"}}, {"middleware", []string{"middleware", "plugins", "interceptors", "guards"}},
	{"utility", []string{"utils", "helpers", "common", "shared", "tools", "templatetags", "pkg"}}, {"config", []string{"config", "constants", "env", "settings", "management", "commands"}},
	{"test", []string{"__tests__", "test", "tests", "spec", "specs", "src/test/java"}}, {"types", []string{"types", "interfaces", "schemas", "contracts", "dtos", "dto", "request", "response"}},
	{"hooks", []string{"hooks"}}, {"state", []string{"store", "state", "reducers", "actions", "slices"}}, {"assets", []string{"assets", "static", "public"}},
	{"entry", []string{"cmd", "bin"}}, {"documentation", []string{"docs", "documentation", "wiki"}}, {"infrastructure", []string{"deploy", "deployment", "infra", "infrastructure", "k8s", "kubernetes", "helm", "charts", "terraform", "tf", "docker"}},
	{"ci-cd", []string{".github", ".gitlab", ".circleci"}},
}

func architecturePattern(group string, nodes []schema.GraphNode, ids []string) string {
	lower := strings.ToLower(strings.ReplaceAll(group, `\`, "/"))
	for _, p := range directoryPatterns {
		for _, v := range p.values {
			if lower == v || strings.HasSuffix(lower, "/"+v) {
				return p.label
			}
		}
	}
	set := map[string]struct{}{}
	for _, id := range ids {
		set[id] = struct{}{}
	}
	for _, n := range nodes {
		if _, ok := set[n.ID]; ok {
			if label := filePattern(n.FilePath); label != "" {
				return label
			}
		}
	}
	return ""
}
func filePattern(file string) string {
	p := strings.ToLower(strings.ReplaceAll(file, `\`, "/"))
	base := path.Base(p)
	switch {
	case isTestFile(base):
		return "test"
	case strings.HasSuffix(base, ".d.ts"), strings.HasSuffix(base, ".graphql"), strings.HasSuffix(base, ".gql"), strings.HasSuffix(base, ".proto"):
		return "types"
	case base == "index.ts" || base == "index.js" || base == "__init__.py" || base == "manage.py" && !strings.Contains(p, "/") || cmdMainGo(p) || (base == "main.rs" || base == "lib.rs") && path.Dir(p) == "src" || base == "application.java" || base == "program.cs" || base == "config.ru":
		return "entry"
	case base == "wsgi.py" || base == "asgi.py" || base == "cargo.toml" || base == "go.mod" || base == "gemfile" || base == "pom.xml" || base == "build.gradle" || base == "composer.json":
		return "config"
	case base == "dockerfile" || strings.HasPrefix(base, "dockerfile.") || strings.HasPrefix(base, "docker-compose") || strings.HasSuffix(base, ".tf") || strings.HasSuffix(base, ".tfvars") || base == "makefile":
		return "infrastructure"
	case strings.HasPrefix(p, ".github/workflows/") || base == ".gitlab-ci.yml" || base == "jenkinsfile":
		return "ci-cd"
	case strings.HasSuffix(base, ".sql"):
		return "data"
	case strings.HasSuffix(base, ".md") || strings.HasSuffix(base, ".rst"):
		return "documentation"
	}
	return ""
}
func cmdMainGo(file string) bool {
	parts := strings.Split(file, "/")
	return len(parts) >= 3 && parts[len(parts)-1] == "main.go" && parts[len(parts)-3] == "cmd"
}

func isTestFile(base string) bool {
	return strings.Contains(base, ".test.") || strings.Contains(base, ".spec.") || strings.HasPrefix(base, "test_") || strings.HasSuffix(base, "_test.go") || strings.HasSuffix(base, "test.java") || strings.HasSuffix(base, "_spec.rb") || strings.HasSuffix(base, "test.php") || strings.HasSuffix(base, "tests.cs")
}

func deploymentSignals(nodes []schema.GraphNode) deploymentTopology {
	out := deploymentTopology{InfraFiles: []string{}}
	for _, n := range nodes {
		p := strings.ToLower(strings.ReplaceAll(n.FilePath, `\`, "/"))
		b := path.Base(p)
		infra := false
		switch {
		case b == "dockerfile" || strings.HasPrefix(b, "dockerfile."):
			out.HasDockerfile = true
			infra = true
		case strings.HasPrefix(b, "docker-compose"):
			out.HasCompose = true
			infra = true
		case strings.Contains(p, "k8s/") || strings.Contains(p, "kubernetes/") || strings.Contains(p, "helm/") || strings.Contains(p, "charts/"):
			out.HasK8s = true
			infra = true
		case strings.HasSuffix(b, ".tf") || strings.HasSuffix(b, ".tfvars") || strings.Contains(p, "terraform/"):
			out.HasTerraform = true
			infra = true
		case strings.HasPrefix(p, ".github/workflows/") || b == ".gitlab-ci.yml" || b == "jenkinsfile":
			out.HasCI = true
			infra = true
		}
		if infra {
			out.InfraFiles = append(out.InfraFiles, n.FilePath)
		}
	}
	return out
}
func dataSignals(nodes []schema.GraphNode) dataPipeline {
	out := dataPipeline{SchemaFiles: []string{}, MigrationFiles: []string{}, DataModelFiles: []string{}, APIHandlerFiles: []string{}}
	for _, n := range nodes {
		p := strings.ToLower(strings.ReplaceAll(n.FilePath, `\`, "/"))
		b := path.Base(p)
		switch {
		case strings.Contains(p, "migrations/"):
			out.MigrationFiles = append(out.MigrationFiles, n.FilePath)
		case strings.HasSuffix(b, ".sql") || strings.HasSuffix(b, ".graphql") || strings.HasSuffix(b, ".gql") || strings.HasSuffix(b, ".proto") || strings.HasSuffix(b, ".prisma"):
			out.SchemaFiles = append(out.SchemaFiles, n.FilePath)
		case strings.Contains(p, "models/") || strings.Contains(p, "entities/") || strings.Contains(p, "repository/"):
			out.DataModelFiles = append(out.DataModelFiles, n.FilePath)
		case strings.Contains(p, "routes/") || strings.Contains(p, "controllers/") || strings.Contains(p, "handlers/"):
			out.APIHandlerFiles = append(out.APIHandlerFiles, n.FilePath)
		}
	}
	return out
}
func documentationSignals(nodes []schema.GraphNode, edges []schema.GraphEdge, groups map[string][]string, nodeGroup map[string]string) docCoverage {
	documented := map[string]bool{}
	documents := map[string]bool{}
	for _, n := range nodes {
		if n.Type == "document" || filePattern(n.FilePath) == "documentation" {
			documents[n.ID] = true
			documented[nodeGroup[n.ID]] = true
		}
	}
	for _, edge := range edges {
		if edge.Type == "documents" && documents[edge.Source] && nodeGroup[edge.Target] != "" {
			documented[nodeGroup[edge.Target]] = true
		}
	}
	undocumented := []string{}
	for group := range groups {
		if !documented[group] {
			undocumented = append(undocumented, group)
		}
	}
	sort.Strings(undocumented)
	ratio := 0.0
	if len(groups) > 0 {
		ratio = float64(len(documented)) / float64(len(groups))
	}
	return docCoverage{len(documented), len(groups), ratio, undocumented}
}
func dependencySignals(inter map[string]int) []dependencyDirection {
	seen := map[string]bool{}
	out := []dependencyDirection{}
	for key, count := range inter {
		parts := strings.Split(key, "\x00")
		a, b := parts[0], parts[1]
		pair := a + "\x00" + b
		if a > b {
			pair = b + "\x00" + a
		}
		if seen[pair] {
			continue
		}
		seen[pair] = true
		reverse := inter[b+"\x00"+a]
		if count > reverse {
			out = append(out, dependencyDirection{a, b})
		} else if reverse > count {
			out = append(out, dependencyDirection{b, a})
		}
	}
	sort.Slice(out, func(i, j int) bool {
		if out[i].Dependent != out[j].Dependent {
			return out[i].Dependent < out[j].Dependent
		}
		return out[i].DependsOn < out[j].DependsOn
	})
	return out
}
