package contracts

import (
	"fmt"
	"regexp"
	"sort"
	"strings"

	"github.com/zzet/gortex/internal/graph"
)

// EnvVarExtractor detects environment variable reads (consumers) and definitions (providers).
type EnvVarExtractor struct{}

var (
	// Consumer patterns.
	envConsumerPatterns = []*regexp.Regexp{
		// Go: os.Getenv("VAR"), os.LookupEnv("VAR")
		regexp.MustCompile(`os\.Getenv\(\s*"([^"]+)"`),
		regexp.MustCompile(`os\.LookupEnv\(\s*"([^"]+)"`),
		// TS: process.env.VAR
		regexp.MustCompile(`process\.env\.([A-Z][A-Z0-9_]+)`),
		// TS: process.env["VAR"]
		regexp.MustCompile(`process\.env\[\s*"([^"]+)"\s*\]`),
		// Python: os.environ["VAR"], os.getenv("VAR")
		regexp.MustCompile(`os\.environ\[\s*"([^"]+)"\s*\]`),
		regexp.MustCompile(`os\.getenv\(\s*"([^"]+)"`),
		// Java: System.getenv("VAR")
		regexp.MustCompile(`System\.getenv\(\s*"([^"]+)"`),
	}

	// Provider patterns.
	envProviderPatterns = []*regexp.Regexp{
		// Go: os.Setenv("VAR", value)
		regexp.MustCompile(`os\.Setenv\(\s*"([^"]+)"`),
	}

	// .env file lines: VAR=value
	envFileLineRe = regexp.MustCompile(`(?m)^([A-Z][A-Z0-9_]+)\s*=`)

	// docker-compose environment entries: - VAR=value or VAR: value
	dockerEnvRe = regexp.MustCompile(`(?m)^\s*-?\s*([A-Z][A-Z0-9_]+)\s*[=:]`)
)

func (e *EnvVarExtractor) SupportedLanguages() []string {
	return []string{"go", "typescript", "javascript", "python", "java", "env", "yaml"}
}

// envCodePrefilterMarkers covers every code-path regex (consumer
// + provider). A file must contain at least one of these before we
// run the seven consumer regexes and one provider regex — typical
// .go/.ts source files without env-var access short-circuit here.
// Env files and docker-compose are routed to separate extractors
// before this check.
var envCodePrefilterMarkers = [][]byte{
	[]byte("Getenv"),      // os.Getenv / System.getenv (both capitalised)
	[]byte("getenv"),      // python os.getenv / lowercase variants
	[]byte("LookupEnv"),   // os.LookupEnv
	[]byte("Setenv"),      // os.Setenv provider
	[]byte("process.env"), // TS/JS
	[]byte("os.environ"),  // python
}

func (e *EnvVarExtractor) Extract(filePath string, src []byte, nodes []*graph.Node, edges []*graph.Edge) []Contract {
	var contracts []Contract

	if isEnvFile(filePath) {
		contracts = append(contracts, e.extractEnvFileProviders(filePath, src)...)
		return contracts
	}
	if isDockerCompose(filePath) {
		contracts = append(contracts, e.extractDockerComposeProviders(filePath, src)...)
		return contracts
	}

	if !srcHasAnyMarker(src, envCodePrefilterMarkers) {
		return nil
	}

	// Thread file nodes into consumer extraction so each os.Getenv
	// site anchors on its enclosing function — required for tools
	// like get_dependencies(handler) to trace which env vars a
	// request needs read.
	fileNodes := filterFileNodes(filePath, nodes)
	sort.Slice(fileNodes, func(i, j int) bool {
		return fileNodes[i].StartLine < fileNodes[j].StartLine
	})
	contracts = append(contracts, e.extractCodeConsumers(filePath, src, fileNodes)...)
	contracts = append(contracts, e.extractCodeProviders(filePath, src)...)

	return contracts
}

func (e *EnvVarExtractor) extractCodeConsumers(filePath string, src []byte, fileNodes []*graph.Node) []Contract {
	var contracts []Contract
	text := string(src)
	lines := strings.Split(text, "\n")

	for _, re := range envConsumerPatterns {
		for _, m := range re.FindAllStringSubmatchIndex(text, -1) {
			varName := text[m[2]:m[3]]
			ln := lineNumber(lines, m[0])
			contracts = append(contracts, Contract{
				ID:         fmt.Sprintf("env::%s", varName),
				Type:       ContractEnv,
				Role:       RoleConsumer,
				SymbolID:   findEnclosingSymbol(fileNodes, ln),
				FilePath:   filePath,
				Line:       ln,
				Meta:       map[string]any{"var": varName},
				Confidence: 0.9,
			})
		}
	}

	return contracts
}

func (e *EnvVarExtractor) extractCodeProviders(filePath string, src []byte) []Contract {
	var contracts []Contract
	text := string(src)
	lines := strings.Split(text, "\n")

	for _, re := range envProviderPatterns {
		for _, m := range re.FindAllStringSubmatchIndex(text, -1) {
			varName := text[m[2]:m[3]]
			contracts = append(contracts, Contract{
				ID:         fmt.Sprintf("env::%s", varName),
				Type:       ContractEnv,
				Role:       RoleProvider,
				FilePath:   filePath,
				Line:       lineNumber(lines, m[0]),
				Meta:       map[string]any{"var": varName},
				Confidence: 0.9,
			})
		}
	}

	return contracts
}

func (e *EnvVarExtractor) extractEnvFileProviders(filePath string, src []byte) []Contract {
	var contracts []Contract
	text := string(src)
	lines := strings.Split(text, "\n")

	for _, m := range envFileLineRe.FindAllStringSubmatchIndex(text, -1) {
		varName := text[m[2]:m[3]]
		contracts = append(contracts, Contract{
			ID:         fmt.Sprintf("env::%s", varName),
			Type:       ContractEnv,
			Role:       RoleProvider,
			FilePath:   filePath,
			Line:       lineNumber(lines, m[0]),
			Meta:       map[string]any{"var": varName, "source": "dotenv"},
			Confidence: 0.95,
		})
	}

	return contracts
}

func (e *EnvVarExtractor) extractDockerComposeProviders(filePath string, src []byte) []Contract {
	var contracts []Contract
	text := string(src)
	lines := strings.Split(text, "\n")

	// Only look within environment: sections.
	envIdx := strings.Index(text, "environment:")
	if envIdx < 0 {
		return nil
	}

	section := text[envIdx:]
	for _, m := range dockerEnvRe.FindAllStringSubmatchIndex(section, -1) {
		varName := section[m[2]:m[3]]
		contracts = append(contracts, Contract{
			ID:         fmt.Sprintf("env::%s", varName),
			Type:       ContractEnv,
			Role:       RoleProvider,
			FilePath:   filePath,
			Line:       lineNumber(lines, envIdx+m[0]),
			Meta:       map[string]any{"var": varName, "source": "docker-compose"},
			Confidence: 0.9,
		})
	}

	return contracts
}

func isEnvFile(path string) bool {
	base := path
	if idx := strings.LastIndex(path, "/"); idx >= 0 {
		base = path[idx+1:]
	}
	return base == ".env" || strings.HasPrefix(base, ".env.")
}

func isDockerCompose(path string) bool {
	base := path
	if idx := strings.LastIndex(path, "/"); idx >= 0 {
		base = path[idx+1:]
	}
	return strings.HasPrefix(base, "docker-compose") && (strings.HasSuffix(base, ".yml") || strings.HasSuffix(base, ".yaml"))
}
