package analyzers

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type BestPractice struct {
	ID          string
	Category    string // security, architecture, performance, quality
	Title       string
	Description string
	Pattern     string
	Language    string
	Framework   string
	Severity    string
	Reference   string
	CodeExample string
}

type PracticesFetcher struct {
	cache      map[string][]BestPractice
	cacheMu    sync.RWMutex
	language   string
	frameworks []string
}

func NewPracticesFetcher() *PracticesFetcher {
	return &PracticesFetcher{
		cache: make(map[string][]BestPractice),
	}
}

func (f *PracticesFetcher) DetectLanguage(path string) string {
	markers := map[string]string{
		"go.mod":           "go",
		"go.sum":           "go",
		"package.json":     "javascript",
		"tsconfig.json":    "typescript",
		"requirements.txt": "python",
		"pyproject.toml":   "python",
		"setup.py":         "python",
		"Cargo.toml":       "rust",
		"pom.xml":          "java",
		"build.gradle":     "java",
		"composer.json":    "php",
		"Gemfile":          "ruby",
	}

	for file, lang := range markers {
		if _, err := os.Stat(filepath.Join(path, file)); err == nil {
			f.language = lang
			return lang
		}
	}
	return "go"
}

func (f *PracticesFetcher) DetectFrameworks(path, language string) []string {
	frameworks := []string{}

	switch language {
	case "go":
		if f.hasImport(path, "github.com/gin-gonic") {
			frameworks = append(frameworks, "gin")
		}
		if f.hasImport(path, "github.com/labstack/echo") {
			frameworks = append(frameworks, "echo")
		}
		if f.hasImport(path, "github.com/gofiber/fiber") {
			frameworks = append(frameworks, "fiber")
		}
		if f.hasImport(path, "gorm.io") {
			frameworks = append(frameworks, "gorm")
		}
		if f.hasImport(path, "github.com/spf13/cobra") {
			frameworks = append(frameworks, "cobra")
		}
		if f.hasImport(path, "k8s.io/client-go") {
			frameworks = append(frameworks, "kubernetes")
		}

	case "typescript", "javascript":
		pkgPath := filepath.Join(path, "package.json")
		if data, err := os.ReadFile(pkgPath); err == nil {
			content := string(data)
			if strings.Contains(content, `"react"`) || strings.Contains(content, `"next"`) {
				frameworks = append(frameworks, "react")
			}
			if strings.Contains(content, `"vue"`) {
				frameworks = append(frameworks, "vue")
			}
			if strings.Contains(content, `"express"`) {
				frameworks = append(frameworks, "express")
			}
			if strings.Contains(content, `"nestjs"`) || strings.Contains(content, `"@nestjs"`) {
				frameworks = append(frameworks, "nestjs")
			}
		}

	case "python":
		reqPath := filepath.Join(path, "requirements.txt")
		if data, err := os.ReadFile(reqPath); err == nil {
			content := strings.ToLower(string(data))
			if strings.Contains(content, "django") {
				frameworks = append(frameworks, "django")
			}
			if strings.Contains(content, "flask") {
				frameworks = append(frameworks, "flask")
			}
			if strings.Contains(content, "fastapi") {
				frameworks = append(frameworks, "fastapi")
			}
		}
	}

	f.frameworks = frameworks
	return frameworks
}

func (f *PracticesFetcher) hasImport(path, importPath string) bool {
	err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
		if err != nil || info.IsDir() || !strings.HasSuffix(filePath, ".go") {
			return nil
		}
		data, err := os.ReadFile(filePath)
		if err != nil {
			return nil
		}
		if strings.Contains(string(data), importPath) {
			return fmt.Errorf("found")
		}
		return nil
	})
	return err != nil
}

func (f *PracticesFetcher) FetchPractices(ctx context.Context, language string, frameworks []string) ([]BestPractice, error) {
	cacheKey := language + ":" + strings.Join(frameworks, ",")

	f.cacheMu.RLock()
	if practices, ok := f.cache[cacheKey]; ok {
		f.cacheMu.RUnlock()
		return practices, nil
	}
	f.cacheMu.RUnlock()

	practices := f.getBuiltInPractices(language, frameworks)

	f.cacheMu.Lock()
	f.cache[cacheKey] = practices
	f.cacheMu.Unlock()

	return practices, nil
}

func (f *PracticesFetcher) getBuiltInPractices(language string, frameworks []string) []BestPractice {
	var practices []BestPractice

	practices = append(practices, f.getLanguagePractices(language)...)

	for _, fw := range frameworks {
		practices = append(practices, f.getFrameworkPractices(fw)...)
	}

	practices = append(practices, f.getSecurityPractices(language)...)
	practices = append(practices, f.getArchitecturePractices()...)
	practices = append(practices, f.getPerformancePractices(language)...)

	return practices
}

func (f *PracticesFetcher) getLanguagePractices(lang string) []BestPractice {
	var practices []BestPractice

	switch lang {
	case "go":
		practices = append(practices, []BestPractice{
			{
				ID:          "go:error-handling",
				Category:    "quality",
				Title:       "Always handle errors explicitly",
				Description: "Never ignore errors. Each error should be handled, wrapped with context, or explicitly logged.",
				Pattern:     `if err != nil`,
				Language:    "go",
				Severity:    "high",
				Reference:   "https://go.dev/blog/error-handling-and-go",
			},
			{
				ID:          "go:defer-in-loop",
				Category:    "performance",
				Title:       "Avoid defer in loops",
				Description: "defer in loops causes resources to be held until function returns. Move loop body to a separate function.",
				Pattern:     `for.*\{[\s\S]*defer`,
				Language:    "go",
				Severity:    "medium",
			},
			{
				ID:          "go:context-first",
				Category:    "architecture",
				Title:       "context.Context should be first parameter",
				Description: "Functions that use context should accept it as the first parameter.",
				Pattern:     `func\s+\w+\([^)]*context\.Context`,
				Language:    "go",
				Severity:    "low",
			},
			{
				ID:          "go:interface-location",
				Category:    "architecture",
				Title:       "Define interfaces where they are used",
				Description: "Interfaces should be defined by the consumer, not the implementer. This promotes loose coupling.",
				Language:    "go",
				Severity:    "medium",
			},
			{
				ID:          "go:exported-comments",
				Category:    "quality",
				Title:       "Exported symbols must have documentation comments",
				Description: "All exported functions, types, and variables should have doc comments starting with their name.",
				Language:    "go",
				Severity:    "low",
				Reference:   "https://go.dev/doc/comment",
			},
			{
				ID:          "go:receiver-type",
				Category:    "architecture",
				Title:       "Use pointer receivers consistently",
				Description: "If any method has a pointer receiver, all methods should have pointer receivers. Use value receivers for small immutable types.",
				Language:    "go",
				Severity:    "low",
			},
			{
				ID:          "go:goroutine-leak",
				Category:    "performance",
				Title:       "Goroutines must have a termination path",
				Description: "Every goroutine should have a clear termination condition, typically via context cancellation or a done channel.",
				Language:    "go",
				Severity:    "high",
			},
		}...)

	case "typescript", "javascript":
		practices = append(practices, []BestPractice{
			{
				ID:          "ts:async-await",
				Category:    "quality",
				Title:       "Prefer async/await over raw Promises",
				Description: "async/await provides better readability and error handling than .then() chains.",
				Language:    "typescript",
				Severity:    "low",
			},
			{
				ID:          "ts:any-type",
				Category:    "quality",
				Title:       "Avoid the any type",
				Description: "Use specific types or unknown instead of any to maintain type safety.",
				Pattern:     `:\s*any\b`,
				Language:    "typescript",
				Severity:    "medium",
			},
			{
				ID:          "ts:null-check",
				Category:    "quality",
				Title:       "Use strict null checks",
				Description: "Enable strictNullChecks in tsconfig.json and handle null/undefined explicitly.",
				Language:    "typescript",
				Severity:    "medium",
			},
		}...)

	case "python":
		practices = append(practices, []BestPractice{
			{
				ID:          "py:type-hints",
				Category:    "quality",
				Title:       "Use type hints for function signatures",
				Description: "Add type annotations to function parameters and return values for better documentation and tooling.",
				Language:    "python",
				Severity:    "low",
			},
			{
				ID:          "py:context-manager",
				Category:    "quality",
				Title:       "Use context managers for resource handling",
				Description: "Always use 'with' statements for files, connections, and other resources.",
				Pattern:     `with\s+\w+`,
				Language:    "python",
				Severity:    "medium",
			},
		}...)
	}

	return practices
}

func (f *PracticesFetcher) getFrameworkPractices(framework string) []BestPractice {
	var practices []BestPractice

	switch framework {
	case "gin", "echo", "fiber", "express":
		practices = append(practices, []BestPractice{
			{
				ID:          "web:input-validation",
				Category:    "security",
				Title:       "Validate all user input",
				Description: "Never trust user input. Validate and sanitize all request parameters, body, and headers.",
				Severity:    "critical",
				Framework:   framework,
			},
			{
				ID:          "web:error-exposure",
				Category:    "security",
				Title:       "Don't expose internal errors to users",
				Description: "Log detailed errors internally but return generic error messages to users.",
				Severity:    "high",
				Framework:   framework,
			},
			{
				ID:          "web:rate-limiting",
				Category:    "security",
				Title:       "Implement rate limiting",
				Description: "Protect endpoints with rate limiting to prevent abuse and DoS attacks.",
				Severity:    "high",
				Framework:   framework,
			},
			{
				ID:          "web:security-headers",
				Category:    "security",
				Title:       "Set security headers",
				Description: "Include X-Content-Type-Options, X-Frame-Options, Content-Security-Policy headers.",
				Severity:    "medium",
				Framework:   framework,
			},
		}...)

	case "react", "vue":
		practices = append(practices, []BestPractice{
			{
				ID:          "frontend:xss-prevention",
				Category:    "security",
				Title:       "Prevent XSS vulnerabilities",
				Description: "Never use dangerouslySetInnerHTML/v-html with user content. Sanitize all user input.",
				Severity:    "critical",
				Framework:   framework,
			},
			{
				ID:          "frontend:dependency-audit",
				Category:    "security",
				Title:       "Audit dependencies regularly",
				Description: "Run npm audit or yarn audit regularly and update vulnerable packages.",
				Severity:    "high",
				Framework:   framework,
			},
		}...)

	case "django", "fastapi", "flask":
		practices = append(practices, []BestPractice{
			{
				ID:          "django:sql-injection",
				Category:    "security",
				Title:       "Use ORM to prevent SQL injection",
				Description: "Never use raw string formatting in SQL queries. Always use parameterized queries or ORM methods.",
				Severity:    "critical",
				Framework:   framework,
			},
			{
				ID:          "django:csrf-protection",
				Category:    "security",
				Title:       "Enable CSRF protection",
				Description: "Ensure CSRF middleware is enabled for all state-changing operations.",
				Severity:    "high",
				Framework:   framework,
			},
		}...)
	}

	return practices
}

func (f *PracticesFetcher) getSecurityPractices(lang string) []BestPractice {
	return []BestPractice{
		{
			ID:          "sec:hardcoded-secrets",
			Category:    "security",
			Title:       "No hardcoded secrets",
			Description: "Never commit secrets, API keys, passwords, or tokens in source code. Use environment variables or secret management.",
			Pattern:     `(password|secret|api_key|apikey|token)\s*[=:]\s*['"][^'"]+['"]`,
			Severity:    "critical",
			Reference:   "https://owasp.org/www-project-web-security-testing-guide/",
		},
		{
			ID:          "sec:sql-injection",
			Category:    "security",
			Title:       "Prevent SQL injection",
			Description: "Use parameterized queries or prepared statements. Never concatenate user input into SQL strings.",
			Severity:    "critical",
			Reference:   "https://owasp.org/www-community/attacks/SQL_Injection",
		},
		{
			ID:          "sec:xss-prevention",
			Category:    "security",
			Title:       "Prevent Cross-Site Scripting (XSS)",
			Description: "Encode output, validate input, use Content-Security-Policy headers.",
			Severity:    "critical",
			Reference:   "https://owasp.org/www-community/attacks/xss/",
		},
		{
			ID:          "sec:insecure-deserialization",
			Category:    "security",
			Title:       "Avoid insecure deserialization",
			Description: "Don't deserialize untrusted data. Validate and sanitize all serialized input.",
			Severity:    "critical",
			Reference:   "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data",
		},
		{
			ID:          "sec:weak-crypto",
			Category:    "security",
			Title:       "Use strong cryptography",
			Description: "Use modern algorithms (AES-256-GCM, SHA-256+, RSA-2048+). Never use MD5, SHA1 for security purposes.",
			Pattern:     `(md5|sha1)\s*\(`,
			Severity:    "high",
		},
		{
			ID:          "sec:logging-sensitive",
			Category:    "security",
			Title:       "Don't log sensitive data",
			Description: "Never log passwords, tokens, credit cards, or PII. Mask or redact sensitive fields.",
			Severity:    "high",
		},
		{
			ID:          "sec:auth-checks",
			Category:    "security",
			Title:       "Implement proper authentication checks",
			Description: "Verify authentication on every protected endpoint. Don't rely on client-side checks.",
			Severity:    "critical",
		},
		{
			ID:          "sec:input-validation",
			Category:    "security",
			Title:       "Validate all input on the server",
			Description: "Client-side validation is for UX. Server-side validation is for security.",
			Severity:    "critical",
		},
	}
}

func (f *PracticesFetcher) getArchitecturePractices() []BestPractice {
	return []BestPractice{
		{
			ID:          "arch:single-responsibility",
			Category:    "architecture",
			Title:       "Single Responsibility Principle",
			Description: "Each module/class should have one reason to change. Split large modules into focused ones.",
			Severity:    "medium",
		},
		{
			ID:          "arch:dependency-injection",
			Category:    "architecture",
			Title:       "Use dependency injection",
			Description: "Inject dependencies rather than creating them internally. This improves testability and flexibility.",
			Severity:    "medium",
		},
		{
			ID:          "arch:layer-separation",
			Category:    "architecture",
			Title:       "Separate concerns by layer",
			Description: "Keep presentation, business logic, and data access layers separate.",
			Severity:    "medium",
		},
		{
			ID:          "arch:interface-segregation",
			Category:    "architecture",
			Title:       "Prefer small, focused interfaces",
			Description: "Clients shouldn't depend on methods they don't use. Split large interfaces.",
			Severity:    "low",
		},
		{
			ID:          "arch:avoid-god-classes",
			Category:    "architecture",
			Title:       "Avoid god classes/modules",
			Description: "Classes with too many responsibilities should be split. Watch for high method/field counts.",
			Severity:    "medium",
		},
		{
			ID:          "arch:circular-dependencies",
			Category:    "architecture",
			Title:       "Eliminate circular dependencies",
			Description: "Circular dependencies indicate tight coupling. Refactor to use dependency inversion.",
			Severity:    "high",
		},
	}
}

func (f *PracticesFetcher) getPerformancePractices(lang string) []BestPractice {
	practices := []BestPractice{
		{
			ID:          "perf:n-plus-one",
			Category:    "performance",
			Title:       "Avoid N+1 query patterns",
			Description: "When iterating over results, avoid making separate queries for each item. Use JOINs or batch loading.",
			Severity:    "high",
		},
		{
			ID:          "perf:unbounded-results",
			Category:    "performance",
			Title:       "Limit query results",
			Description: "Always paginate or limit query results to prevent memory exhaustion.",
			Severity:    "medium",
		},
		{
			ID:          "perf:connection-pooling",
			Category:    "performance",
			Title:       "Use connection pooling",
			Description: "Don't create new connections per request. Use connection pools for databases and HTTP clients.",
			Severity:    "high",
		},
		{
			ID:          "perf:caching",
			Category:    "performance",
			Title:       "Cache expensive operations",
			Description: "Cache frequently accessed, rarely changing data. Consider memoization for expensive computations.",
			Severity:    "medium",
		},
		{
			ID:          "perf:blocking-in-hot-path",
			Category:    "performance",
			Title:       "Avoid blocking operations in hot paths",
			Description: "Move I/O, network calls, and heavy computations out of request handlers when possible.",
			Severity:    "medium",
		},
	}

	if lang == "go" {
		practices = append(practices, []BestPractice{
			{
				ID:          "go:perf:string-concat",
				Category:    "performance",
				Title:       "Use strings.Builder for string concatenation",
				Description: "In loops, use strings.Builder instead of += for efficient string concatenation.",
				Pattern:     `for[\s\S]*\+=.*["` + "`" + `]`,
				Language:    "go",
				Severity:    "medium",
			},
			{
				ID:          "go:perf:slice-prealloc",
				Category:    "performance",
				Title:       "Pre-allocate slices when size is known",
				Description: "Use make([]T, 0, capacity) when you know the final size to avoid reallocations.",
				Language:    "go",
				Severity:    "low",
			},
			{
				ID:          "go:perf:json-marshal",
				Category:    "performance",
				Title:       "Consider streaming JSON for large payloads",
				Description: "For large JSON, use json.Encoder/Decoder instead of Marshal/Unmarshal to reduce allocations.",
				Language:    "go",
				Severity:    "low",
			},
		}...)
	}

	return practices
}

func (f *PracticesFetcher) GetPracticesByCategory(category string) []BestPractice {
	f.cacheMu.RLock()
	defer f.cacheMu.RUnlock()

	var result []BestPractice
	for _, practices := range f.cache {
		for _, p := range practices {
			if p.Category == category {
				result = append(result, p)
			}
		}
	}
	return result
}

func (f *PracticesFetcher) GetAllPractices() []BestPractice {
	f.cacheMu.RLock()
	defer f.cacheMu.RUnlock()

	var result []BestPractice
	seen := make(map[string]bool)
	for _, practices := range f.cache {
		for _, p := range practices {
			if !seen[p.ID] {
				result = append(result, p)
				seen[p.ID] = true
			}
		}
	}
	return result
}
