package scanner

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

const starterGraphIgnoreHeader = `# .agentteam-insightignore — patterns for files/dirs to exclude from analysis
# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs)
# Lines below are suggestions — uncomment to activate.
# Use ! prefix to force-include something excluded by defaults.
#
# Built-in defaults (always excluded unless negated):
#   node_modules/, .git/, dist/, build/, obj/, *.lock, *.min.js, etc.
#
`

var starterExactDirNames = map[string]struct{}{
	"__tests__":        {},
	"test":             {},
	"tests":            {},
	"fixtures":         {},
	"testdata":         {},
	"docs":             {},
	"examples":         {},
	"scripts":          {},
	"migrations":       {},
	".storybook":       {},
	"unittests":        {},
	"integrationtests": {},
}

var starterSuffixDirGlobs = []string{".tests", ".unittests", ".integrationtests"}

var starterTestPatternGroups = []struct {
	label    string
	patterns []string
}{
	{label: "JS / TS", patterns: []string{"*.test.*", "*.spec.*", "*.snap"}},
	{label: "C# / .NET", patterns: []string{"**/*Tests.cs", "**/*Test.cs", "**/*Fixture.cs", "**/*.Tests.csproj"}},
	{label: "Java / Kotlin", patterns: []string{"**/src/test/**", "**/*Test.java", "**/*IT.java", "**/*Spec.kt"}},
	{label: "Go", patterns: []string{"**/*_test.go"}},
}

// EnsureStarterIgnore creates the canonical graph builder starter ignore file when absent.
func EnsureStarterIgnore(projectRoot string) (bool, string, error) {
	return EnsureStarterIgnoreForRoot(projectRoot, projectRoot)
}

// EnsureStarterIgnoreForRoot writes the canonical ignore file under projectRoot,
// while deriving suggestions from sourceRoot. The roots differ when AgentTeam writes
// graph artifacts outside the input repository.
func EnsureStarterIgnoreForRoot(projectRoot string, sourceRoot string) (bool, string, error) {
	path := StarterIgnorePath(projectRoot)
	if _, err := os.Stat(path); err == nil {
		return false, path, nil
	} else if !os.IsNotExist(err) {
		return false, path, err
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return false, path, err
	}
	if err := os.WriteFile(path, []byte(GenerateStarterIgnore(sourceRoot)), 0o644); err != nil {
		return false, path, err
	}
	return true, path, nil
}

func StarterIgnorePath(projectRoot string) string {
	return filepath.Join(projectRoot, ".agentteam-insightignore")
}

func GenerateStarterIgnore(projectRoot string) string {
	sections := []string{starterGraphIgnoreHeader}
	gitignorePatterns := parseGitignorePatterns(filepath.Join(projectRoot, ".gitignore"))
	if len(gitignorePatterns) > 0 {
		sections = append(sections, "# --- From .gitignore (uncomment to exclude) ---\n")
		for _, pattern := range gitignorePatterns {
			if isCoveredByDefaults(pattern) {
				continue
			}
			sections = append(sections, "# "+pattern)
		}
		sections = append(sections, "")
	}

	detected := detectStarterDirectories(projectRoot)
	if len(detected) > 0 {
		sections = append(sections, "# --- Detected directories (uncomment to exclude) ---\n")
		for _, pattern := range detected {
			sections = append(sections, "# "+pattern)
		}
		sections = append(sections, "")
	}

	sections = append(sections, "# --- Test file patterns (uncomment to exclude) ---\n")
	for _, group := range starterTestPatternGroups {
		sections = append(sections, "# "+group.label)
		for _, pattern := range group.patterns {
			sections = append(sections, "# "+pattern)
		}
	}
	sections = append(sections, "")
	return strings.Join(sections, "\n")
}

func parseGitignorePatterns(path string) []string {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil
	}
	patterns := []string{}
	for _, line := range strings.Split(string(data), "\n") {
		line = strings.TrimSpace(strings.TrimSuffix(line, "\r"))
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		patterns = append(patterns, line)
	}
	return patterns
}

func isCoveredByDefaults(pattern string) bool {
	normalize := func(value string) string { return strings.TrimRight(filepath.ToSlash(value), "/") }
	pattern = normalize(pattern)
	for _, item := range defaultIgnorePatterns {
		if normalize(item.pattern) == pattern {
			return true
		}
	}
	return false
}

func detectStarterDirectories(projectRoot string) []string {
	entries, err := os.ReadDir(projectRoot)
	if err != nil {
		return nil
	}
	matches := []string{}
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		lower := strings.ToLower(entry.Name())
		if _, ok := starterExactDirNames[lower]; ok {
			matches = append(matches, entry.Name()+"/")
			continue
		}
		for _, suffix := range starterSuffixDirGlobs {
			if strings.HasSuffix(lower, suffix) {
				matches = append(matches, entry.Name()+"/")
				break
			}
		}
	}
	sort.Strings(matches)
	return matches
}

func StarterIgnoreConfirmationMessage(generated bool, path string) string {
	path = filepath.ToSlash(path)
	if generated {
		return fmt.Sprintf("Generated `%s` with suggested exclusions based on your project structure. Please review it and uncomment any patterns you'd like to exclude from analysis. When ready, confirm to continue.", path)
	}
	return fmt.Sprintf("Found `%s`. Review it if needed, then confirm to continue.", path)
}
