package generic

import (
	"fmt"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"unicode"

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

func addRegexImports(analysis *extractor.StructuralAnalysis, text string, patterns []importPattern) {
	for _, pattern := range patterns {
		for _, match := range findLineMatches(text, pattern.RE) {
			value := ""
			if pattern.Group-1 >= 0 && pattern.Group-1 < len(match.Groups) {
				value = match.Groups[pattern.Group-1]
			}
			value = strings.TrimSpace(value)
			if value == "" {
				continue
			}
			analysis.Imports = append(analysis.Imports, extractor.Import{Path: value})
		}
	}
}

type importPattern struct {
	RE    *regexp.Regexp
	Group int
}

type lineMatch struct {
	Text   string
	Name   string
	Line   int
	Groups []string
}

func findLineMatches(text string, re *regexp.Regexp) []lineMatch {
	lineStarts := []int{0}
	for i, r := range text {
		if r == '\n' {
			lineStarts = append(lineStarts, i+1)
		}
	}
	matches := re.FindAllStringSubmatchIndex(text, -1)
	out := make([]lineMatch, 0, len(matches))
	for _, match := range matches {
		groups := []string{}
		for i := 2; i+1 < len(match); i += 2 {
			if match[i] < 0 || match[i+1] < 0 {
				groups = append(groups, "")
				continue
			}
			groups = append(groups, text[match[i]:match[i+1]])
		}
		name := ""
		if len(groups) > 0 {
			name = strings.TrimSpace(groups[len(groups)-1])
		}
		out = append(out, lineMatch{Text: text[match[0]:match[1]], Name: name, Line: lineForOffset(lineStarts, match[0]), Groups: groups})
	}
	return out
}

func lineForOffset(lineStarts []int, offset int) int {
	i := sort.Search(len(lineStarts), func(i int) bool { return lineStarts[i] > offset })
	if i == 0 {
		return 1
	}
	return i
}

func addFunction(analysis *extractor.StructuralAnalysis, name string, line int, exported bool) {
	name = strings.TrimSpace(name)
	if name == "" {
		return
	}
	analysis.Functions = append(analysis.Functions, extractor.Function{Name: name, Exported: exported, LineRange: [2]int{line, line}})
	if exported {
		analysis.Exports = append(analysis.Exports, name)
	}
}

func addType(analysis *extractor.StructuralAnalysis, name string, kind string, line int, exported bool) {
	name = strings.TrimSpace(name)
	if name == "" {
		return
	}
	analysis.Types = append(analysis.Types, extractor.Type{Name: name, Kind: kind, Exported: exported, LineRange: [2]int{line, line}})
	if exported {
		analysis.Exports = append(analysis.Exports, name)
	}
}

func addCallMatches(analysis *extractor.StructuralAnalysis, text string, re *regexp.Regexp, keywords map[string]bool) {
	current := ""
	functionLines := map[int]string{}
	for _, fn := range analysis.Functions {
		functionLines[fn.LineRange[0]] = fn.Name
	}
	lines := strings.Split(text, "\n")
	for i, line := range lines {
		lineNo := i + 1
		if name := functionLines[lineNo]; name != "" {
			current = name
		}
		for _, match := range re.FindAllStringSubmatch(line, -1) {
			if len(match) < 2 {
				continue
			}
			callee := strings.TrimSpace(match[1])
			if current == "" || callee == "" || keywords[callee] || callee == current {
				continue
			}
			analysis.Calls = append(analysis.Calls, extractor.Call{Caller: current, Callee: callee, Line: lineNo})
		}
	}
}

func finalize(analysis *extractor.StructuralAnalysis) {
	analysis.Imports = dedupeImports(analysis.Imports)
	analysis.Functions = dedupeFunctions(analysis.Functions)
	analysis.Types = dedupeTypes(analysis.Types)
	analysis.Exports = dedupeSortedStrings(analysis.Exports)
	analysis.Calls = dedupeCalls(analysis.Calls)
	analysis.SubNodes = dedupeSubNodes(analysis.SubNodes)
	if !analysis.HasStructuralAnalysis {
		analysis.HasStructuralAnalysis = len(analysis.Imports) > 0 || len(analysis.Functions) > 0 || len(analysis.Types) > 0 || len(analysis.Exports) > 0 || len(analysis.Calls) > 0 || len(analysis.SubNodes) > 0
	}
}

func subNode(nodeType string, name string, summary string, tags []string, line int) extractor.SubNode {
	return extractor.SubNode{Type: nodeType, Name: strings.TrimSpace(name), Summary: summary, Tags: tags, LineRange: [2]int{line, line}}
}

func isExportedLine(line string, name string) bool {
	if strings.Contains(line, "export ") {
		return true
	}
	if name == "" {
		return false
	}
	runes := []rune(name)
	return len(runes) > 0 && unicode.IsUpper(runes[0])
}

func splitSpecifiers(value string) []string {
	parts := strings.Split(value, ",")
	out := []string{}
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			out = append(out, part)
		}
	}
	sort.Strings(out)
	return out
}

func normalizeRustPath(path string) string {
	path = strings.TrimSpace(path)
	path = strings.TrimSuffix(path, "::self")
	path = strings.TrimSuffix(path, "::prelude::*")
	path = strings.ReplaceAll(path, "::{", "::")
	path = strings.ReplaceAll(path, "}", "")
	path = strings.ReplaceAll(path, " ", "")
	if idx := strings.Index(path, ","); idx >= 0 {
		path = path[:idx]
	}
	return path
}

func cleanSQLName(value string) string {
	return strings.Trim(strings.TrimSpace(value), "\"`[]")
}

func looksLikeOpenAPI(path string, text string) bool {
	base := strings.ToLower(filepath.Base(path))
	return strings.Contains(strings.ToLower(text), "openapi:") || strings.Contains(strings.ToLower(text), `"openapi"`) || strings.Contains(base, "openapi") || strings.Contains(base, "swagger")
}

func isKubernetesYAML(text string) bool {
	lower := strings.ToLower(text)
	return strings.Contains(lower, "apiversion:") && strings.Contains(lower, "kind:") && strings.Contains(lower, "metadata:")
}

func hasOpenAPIMarker(decoded map[string]any) bool {
	if _, ok := decoded["openapi"]; ok {
		return true
	}
	if _, ok := decoded["swagger"]; ok {
		return true
	}
	return false
}

func metadataName(doc map[string]any) string {
	meta, ok := doc["metadata"].(map[string]any)
	if !ok {
		return ""
	}
	return stringValue(meta["name"])
}

func stringValue(value any) string {
	switch value := value.(type) {
	case nil:
		return ""
	case string:
		return strings.TrimSpace(value)
	default:
		return strings.TrimSpace(fmt.Sprint(value))
	}
}

func httpMethods() map[string]bool {
	return map[string]bool{"get": true, "put": true, "post": true, "delete": true, "patch": true, "options": true, "head": true, "trace": true}
}

func dedupeImports(in []extractor.Import) []extractor.Import {
	seen := map[string]struct{}{}
	out := []extractor.Import{}
	for _, item := range in {
		item.Path = strings.TrimSpace(item.Path)
		item.Alias = strings.TrimSpace(item.Alias)
		if item.Path == "" {
			continue
		}
		key := item.Path + "\x00" + item.Alias + "\x00" + strings.Join(item.Specifiers, ",")
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Path+"\x00"+out[i].Alias < out[j].Path+"\x00"+out[j].Alias })
	return out
}

func dedupeFunctions(in []extractor.Function) []extractor.Function {
	seen := map[string]struct{}{}
	out := []extractor.Function{}
	for _, item := range in {
		if item.Name == "" {
			continue
		}
		key := item.Name + "\x00" + fmt.Sprint(item.LineRange)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
	return out
}

func dedupeTypes(in []extractor.Type) []extractor.Type {
	seen := map[string]struct{}{}
	out := []extractor.Type{}
	for _, item := range in {
		if item.Name == "" {
			continue
		}
		key := item.Name + "\x00" + item.Kind
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
	return out
}

func dedupeCalls(in []extractor.Call) []extractor.Call {
	seen := map[string]struct{}{}
	out := []extractor.Call{}
	for _, item := range in {
		if item.Caller == "" || item.Callee == "" {
			continue
		}
		key := item.Caller + "\x00" + item.Callee + "\x00" + fmt.Sprint(item.Line)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool {
		if out[i].Caller == out[j].Caller {
			if out[i].Callee == out[j].Callee {
				return out[i].Line < out[j].Line
			}
			return out[i].Callee < out[j].Callee
		}
		return out[i].Caller < out[j].Caller
	})
	return out
}

func dedupeSubNodes(in []extractor.SubNode) []extractor.SubNode {
	seen := map[string]struct{}{}
	out := []extractor.SubNode{}
	for _, item := range in {
		item.Type = strings.ToLower(strings.TrimSpace(item.Type))
		item.Name = strings.TrimSpace(item.Name)
		if item.Type == "" || item.Name == "" {
			continue
		}
		key := item.Type + "\x00" + item.Name
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		if item.Tags == nil {
			item.Tags = []string{}
		}
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Type+"\x00"+out[i].Name < out[j].Type+"\x00"+out[j].Name })
	return out
}

func dedupeSortedStrings(in []string) []string {
	seen := map[string]struct{}{}
	out := []string{}
	for _, item := range in {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, ok := seen[item]; ok {
			continue
		}
		seen[item] = struct{}{}
		out = append(out, item)
	}
	sort.Strings(out)
	return out
}

func jsKeywords() map[string]bool {
	return keywordMap("if", "for", "while", "switch", "catch", "function", "return", "typeof", "import", "require")
}

func pythonKeywords() map[string]bool {
	return keywordMap("if", "for", "while", "with", "return", "class", "def", "print", "len", "range", "super")
}

func rustKeywords() map[string]bool {
	return keywordMap("if", "for", "while", "agentteam", "match", "return", "Some", "Ok", "Err", "vec")
}

func javaKeywords() map[string]bool {
	return keywordMap("if", "for", "while", "switch", "catch", "return", "new", "super", "this", "synchronized")
}

func phpKeywords() map[string]bool {
	return keywordMap("if", "foreach", "for", "while", "switch", "catch", "return", "array", "echo", "isset")
}

func cKeywords() map[string]bool {
	return keywordMap("if", "for", "while", "switch", "return", "sizeof", "catch")
}

func rubyKeywords() map[string]bool {
	return keywordMap("if", "elsif", "while", "until", "return", "puts", "require", "class", "module", "def")
}

func keywordMap(values ...string) map[string]bool {
	out := map[string]bool{}
	for _, value := range values {
		out[value] = true
	}
	return out
}

func isControlKeyword(value string) bool {
	switch value {
	case "if", "for", "while", "switch", "catch", "return", "new", "sizeof":
		return true
	default:
		return false
	}
}
