package git

import (
	"path"
	"strings"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/events"
)

var languageByExt = map[string]string{
	".go":    "go",
	".js":    "javascript",
	".jsx":   "javascript",
	".ts":    "typescript",
	".tsx":   "typescript",
	".py":    "python",
	".java":  "java",
	".dart":  "dart",
	".lua":   "lua",
	".rs":    "rust",
	".rb":    "ruby",
	".php":   "php",
	".inc":   "php",
	".phtml": "php",
	".cs":    "csharp",
	".cpp":   "cpp",
	".c":     "c",
	".h":     "c",
	".html":  "html",
	".htm":   "html",
	".vue":   "vue",
	".css":   "css",
	".scss":  "scss",
	".sass":  "sass",
	".less":  "less",
	".md":    "markdown",
	".rst":   "rst",
	".yaml":  "yaml",
	".yml":   "yaml",
	".json":  "json",
	".toml":  "toml",
	".sh":    "shell",
	".sql":   "sql",
}

var assetExtensions = map[string]struct{}{
	".png": {}, ".jpg": {}, ".jpeg": {}, ".gif": {}, ".svg": {},
	".ico": {}, ".webp": {}, ".woff": {}, ".woff2": {}, ".ttf": {},
	".eot": {}, ".pdf": {}, ".zip": {}, ".tar": {}, ".gz": {},
}

// vendoredDirs are dependency trees checked into the repository. They are not
// first-party code, so indexing them wastes pipeline work and pollutes retrieval.
var vendoredDirs = []string{"vendor", "node_modules", "bower_components"}

// isVendoredPath reports whether any path segment is a vendored dependency dir.
// Matches at any depth: cake.admedia.com keeps composer deps under html/vendor/.
func isVendoredPath(normalized string) bool {
	lower := strings.ToLower(normalized)
	for _, dir := range vendoredDirs {
		if strings.HasPrefix(lower, dir+"/") || strings.Contains(lower, "/"+dir+"/") {
			return true
		}
	}
	return false
}

// ClassifyFile returns language and file_kind for a repository path.
func ClassifyFile(filePath string) (language string, fileKind events.FileKind) {
	normalized := strings.ReplaceAll(filePath, `\`, "/")
	base := path.Base(normalized)
	ext := strings.ToLower(path.Ext(base))

	if isVendoredPath(normalized) {
		return "unknown", events.FileKindUnknown
	}

	if lang := templateLanguage(normalized); lang != "" {
		if kind := classifyByRules(normalized, base, ext); kind != "" {
			return lang, kind
		}
		return lang, events.FileKindCode
	}

	if kind := classifyByRules(normalized, base, ext); kind != "" {
		return languageForExt(ext), kind
	}

	lang := languageForExt(ext)
	if lang != "unknown" && lang != "" {
		return lang, events.FileKindCode
	}
	return "unknown", events.FileKindUnknown
}

func templateLanguage(normalized string) string {
	lower := strings.ToLower(normalized)
	switch {
	case strings.HasSuffix(lower, ".blade.php"):
		return "blade"
	case strings.HasSuffix(lower, ".twig"):
		return "twig"
	case strings.HasSuffix(lower, ".vue"):
		return "vue"
	case strings.HasSuffix(lower, ".html"), strings.HasSuffix(lower, ".htm"):
		return "html"
	case strings.HasSuffix(lower, ".scss"):
		return "scss"
	case strings.HasSuffix(lower, ".sass"):
		return "sass"
	case strings.HasSuffix(lower, ".less"):
		return "less"
	case strings.HasSuffix(lower, ".css"):
		return "css"
	default:
		return ""
	}
}

func classifyByRules(normalized, base, ext string) events.FileKind {
	if strings.HasPrefix(normalized, "docs/") || ext == ".md" {
		return events.FileKindDocs
	}
	if ext == ".rst" {
		return events.FileKindUnknown
	}
	if isTestPath(normalized, base, ext) {
		return events.FileKindTest
	}
	if isConfigPath(base, ext) {
		return events.FileKindConfig
	}
	if _, ok := assetExtensions[ext]; ok {
		return events.FileKindAsset
	}
	if isTemplateOrStylePath(normalized, ext) {
		return events.FileKindCode
	}
	if _, ok := languageByExt[ext]; ok {
		return events.FileKindCode
	}
	return ""
}

func isTemplateOrStylePath(normalized, ext string) bool {
	lower := strings.ToLower(normalized)
	if strings.HasSuffix(lower, ".blade.php") || strings.HasSuffix(lower, ".twig") || strings.HasSuffix(lower, ".vue") {
		return true
	}
	switch ext {
	case ".html", ".htm", ".css", ".scss", ".sass", ".less":
		return true
	}
	return false
}

func isTestPath(normalized, base, ext string) bool {
	if strings.HasPrefix(normalized, "test/") || strings.Contains(normalized, "/test/") {
		return true
	}
	if strings.HasPrefix(normalized, "tests/") || strings.Contains(normalized, "/tests/") {
		return true
	}
	if strings.HasPrefix(normalized, "__tests__/") || strings.Contains(normalized, "/__tests__/") {
		return true
	}
	if strings.HasSuffix(base, "_test.go") {
		return true
	}
	if strings.HasSuffix(base, ".test.js") || strings.HasSuffix(base, ".spec.js") {
		return true
	}
	if strings.HasSuffix(base, ".test.ts") || strings.HasSuffix(base, ".spec.ts") {
		return true
	}
	if strings.HasPrefix(base, "test_") && ext == ".py" {
		return true
	}
	return false
}

func isConfigPath(base, ext string) bool {
	if isIgnoredBasename(base) {
		return true
	}
	switch ext {
	case ".yaml", ".yml", ".json", ".toml", ".ini", ".env", ".mod", ".sum", ".sh":
		return true
	}
	if strings.HasPrefix(base, "Dockerfile") {
		return true
	}
	return false
}

func isIgnoredBasename(base string) bool {
	switch strings.ToLower(base) {
	case ".gitignore", ".dockerignore", ".gitattributes", ".editorconfig",
		".prettierignore", ".eslintignore", ".npmignore",
		"package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml",
		"composer.lock", "gemfile.lock", "poetry.lock", "cargo.lock", "go.sum":
		return true
	default:
		return false
	}
}

func languageForExt(ext string) string {
	if ext == "" {
		return "unknown"
	}
	if lang, ok := languageByExt[ext]; ok {
		return lang
	}
	if ext != "" {
		return strings.TrimPrefix(ext, ".")
	}
	return "unknown"
}
