package analyzers

import (
	"context"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"strings"

	"github.com/yourorg/devour/internal/quality"
)

type LargeFileDetector struct {
	*quality.BaseDetector
	maxLOC int
}

func NewLargeFileDetector(finder quality.FileFinder) *LargeFileDetector {
	return &LargeFileDetector{
		BaseDetector: quality.NewBaseDetector("large_file", quality.SeverityT3, finder),
		maxLOC:       500,
	}
}

func (d *LargeFileDetector) Name() string {
	return "large_file"
}

func (d *LargeFileDetector) Severity() quality.Severity {
	return quality.SeverityT3
}

func (d *LargeFileDetector) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	files, err := d.FindFiles(path, "go")
	if err != nil {
		return nil, err
	}

	var findings []quality.Finding
	for _, file := range files {
		loc, err := countLines(file)
		if err != nil {
			findings = append(findings, quality.Finding{
				ID:          fmt.Sprintf("detector_read_error::large_file::%s", file),
				Type:        "detector_error",
				Title:       "Large file detector could not read file",
				Description: fmt.Sprintf("Failed to count lines in %s: %v", filepath.Base(file), err),
				File:        file,
				Line:        1,
				Severity:    quality.SeverityT2,
				Score:       0,
				Status:      quality.StatusOpen,
				Metadata: map[string]string{
					"detector": "large_file",
					"error":    err.Error(),
				},
			})
			continue
		}

		if loc > d.maxLOC {
			finding := quality.Finding{
				ID:          fmt.Sprintf("large_file::%s", file),
				Type:        "large_file",
				Title:       fmt.Sprintf("Large file detected: %d lines", loc),
				Description: fmt.Sprintf("File '%s' has %d lines (max: %d). Consider splitting into smaller, focused files.", filepath.Base(file), loc, d.maxLOC),
				File:        file,
				Line:        1,
				Severity:    quality.SeverityT3,
				Score:       (loc - d.maxLOC) / 50,
				Status:      quality.StatusOpen,
				Metadata: map[string]string{
					"loc":     fmt.Sprintf("%d", loc),
					"max_loc": fmt.Sprintf("%d", d.maxLOC),
				},
			}
			findings = append(findings, finding)
		}
	}

	return findings, nil
}

type GodStructDetector struct {
	*quality.BaseDetector
	maxFields  int
	maxMethods int
}

func NewGodStructDetector(finder quality.FileFinder) *GodStructDetector {
	return &GodStructDetector{
		BaseDetector: quality.NewBaseDetector("god_struct", quality.SeverityT3, finder),
		maxFields:    15,
		maxMethods:   20,
	}
}

func (d *GodStructDetector) Name() string {
	return "god_struct"
}

func (d *GodStructDetector) Severity() quality.Severity {
	return quality.SeverityT3
}

func (d *GodStructDetector) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	files, err := d.FindFiles(path, "go")
	if err != nil {
		return nil, err
	}

	var findings []quality.Finding
	for _, file := range files {
		fileFindings, err := d.analyzeFile(file)
		if err != nil {
			return nil, fmt.Errorf("analyze god struct in %q: %w", file, err)
		}
		findings = append(findings, fileFindings...)
	}

	return findings, nil
}

func (d *GodStructDetector) analyzeFile(path string) ([]quality.Finding, error) {
	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, path, nil, 0)
	if err != nil {
		return nil, fmt.Errorf("parse %s: %w", path, err)
	}

	methodCounts := make(map[string]int)
	for _, decl := range node.Decls {
		if fn, ok := decl.(*ast.FuncDecl); ok && fn.Recv != nil {
			for _, field := range fn.Recv.List {
				for _, name := range field.Names {
					methodCounts[name.Name]++
				}
				if len(field.Names) == 0 {
					if star, ok := field.Type.(*ast.StarExpr); ok {
						if ident, ok := star.X.(*ast.Ident); ok {
							methodCounts[ident.Name]++
						}
					} else if ident, ok := field.Type.(*ast.Ident); ok {
						methodCounts[ident.Name]++
					}
				}
			}
		}
	}

	var findings []quality.Finding
	for _, decl := range node.Decls {
		gen, ok := decl.(*ast.GenDecl)
		if !ok || gen.Tok != token.TYPE {
			continue
		}

		for _, spec := range gen.Specs {
			typeSpec, ok := spec.(*ast.TypeSpec)
			if !ok {
				continue
			}

			structType, ok := typeSpec.Type.(*ast.StructType)
			if !ok {
				continue
			}

			fieldCount := len(structType.Fields.List)
			methodCount := methodCounts[typeSpec.Name.Name]

			if fieldCount > d.maxFields {
				pos := fset.Position(typeSpec.Pos())
				finding := quality.Finding{
					ID:          fmt.Sprintf("god_struct::%s::%s", path, typeSpec.Name.Name),
					Type:        "god_struct",
					Title:       fmt.Sprintf("God struct detected: %s", typeSpec.Name.Name),
					Description: fmt.Sprintf("Struct '%s' has %d fields (max: %d). Consider breaking it into smaller, focused structs.", typeSpec.Name.Name, fieldCount, d.maxFields),
					File:        path,
					Line:        pos.Line,
					Severity:    quality.SeverityT3,
					Score:       (fieldCount - d.maxFields) * 2,
					Status:      quality.StatusOpen,
					Metadata: map[string]string{
						"struct_name": typeSpec.Name.Name,
						"field_count": fmt.Sprintf("%d", fieldCount),
						"max_fields":  fmt.Sprintf("%d", d.maxFields),
					},
				}
				findings = append(findings, finding)
			}

			if methodCount > d.maxMethods {
				pos := fset.Position(typeSpec.Pos())
				finding := quality.Finding{
					ID:          fmt.Sprintf("god_struct_methods::%s::%s", path, typeSpec.Name.Name),
					Type:        "god_struct",
					Title:       fmt.Sprintf("God struct (methods): %s", typeSpec.Name.Name),
					Description: fmt.Sprintf("Struct '%s' has %d methods (max: %d). Consider splitting responsibilities.", typeSpec.Name.Name, methodCount, d.maxMethods),
					File:        path,
					Line:        pos.Line,
					Severity:    quality.SeverityT3,
					Score:       (methodCount - d.maxMethods) * 2,
					Status:      quality.StatusOpen,
					Metadata: map[string]string{
						"struct_name":  typeSpec.Name.Name,
						"method_count": fmt.Sprintf("%d", methodCount),
						"max_methods":  fmt.Sprintf("%d", d.maxMethods),
					},
				}
				findings = append(findings, finding)
			}
		}
	}

	return findings, nil
}

type DebugLogDetector struct {
	*quality.BaseDetector
}

func NewDebugLogDetector(finder quality.FileFinder) *DebugLogDetector {
	return &DebugLogDetector{
		BaseDetector: quality.NewBaseDetector("debug_log", quality.SeverityT1, finder),
	}
}

func (d *DebugLogDetector) Name() string {
	return "debug_log"
}

func (d *DebugLogDetector) Severity() quality.Severity {
	return quality.SeverityT1
}

func (d *DebugLogDetector) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	files, err := d.FindFiles(path, "go")
	if err != nil {
		return nil, err
	}

	var findings []quality.Finding
	for _, file := range files {
		fileFindings, err := d.analyzeFile(file)
		if err != nil {
			return nil, fmt.Errorf("analyze debug logs in %q: %w", file, err)
		}
		findings = append(findings, fileFindings...)
	}

	return findings, nil
}

func (d *DebugLogDetector) analyzeFile(path string) ([]quality.Finding, error) {
	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, path, nil, 0)
	if err != nil {
		return nil, fmt.Errorf("parse %s: %w", path, err)
	}
	normPath := filepath.ToSlash(path)
	if strings.Contains(normPath, "internal/ui/") || strings.Contains(normPath, "examples/") {
		return nil, nil
	}

	debugPatterns := []string{
		"log.Print",
		"log.Println",
		"log.Printf",
		"log.Fatal",
		"log.Fatalf",
		"log.Fatalln",
	}

	cliPatterns := []string{
		"fmt.Print",
		"fmt.Println",
		"fmt.Printf",
	}

	var findings []quality.Finding
	ast.Inspect(node, func(n ast.Node) bool {
		call, ok := n.(*ast.CallExpr)
		if !ok {
			return true
		}

		callStr := exprToString(call.Fun)

		for _, pattern := range debugPatterns {
			if callStr == pattern || strings.HasPrefix(callStr, pattern) {
				if strings.HasSuffix(normPath, "_test.go") || strings.HasPrefix(normPath, "cmd/") || strings.Contains(normPath, "/cmd/") {
					return true
				}

				pos := fset.Position(call.Pos())
				finding := quality.Finding{
					ID:          fmt.Sprintf("debug_log::%s::%d", path, pos.Line),
					Type:        "debug_log",
					Title:       "Debug log statement detected",
					Description: fmt.Sprintf("Found '%s' statement. Consider using structured logging instead.", callStr),
					File:        path,
					Line:        pos.Line,
					Severity:    quality.SeverityT1,
					Score:       2,
					Status:      quality.StatusOpen,
					Metadata: map[string]string{
						"call": callStr,
					},
				}
				findings = append(findings, finding)
				break
			}
		}

		if strings.HasPrefix(normPath, "cmd/") || strings.Contains(normPath, "/cmd/") {
			return true
		}

		for _, pattern := range cliPatterns {
			if callStr == pattern || strings.HasPrefix(callStr, pattern) {
				pos := fset.Position(call.Pos())
				finding := quality.Finding{
					ID:          fmt.Sprintf("debug_log::%s::%d", path, pos.Line),
					Type:        "debug_log",
					Title:       "Potential debug print in non-CLI code",
					Description: fmt.Sprintf("Found '%s' in library code. Consider using structured logging or returning errors.", callStr),
					File:        path,
					Line:        pos.Line,
					Severity:    quality.SeverityT1,
					Score:       2,
					Status:      quality.StatusOpen,
					Metadata: map[string]string{
						"call": callStr,
					},
				}
				findings = append(findings, finding)
				break
			}
		}

		return true
	})

	return findings, nil
}

type GodFunctionDetector struct {
	*quality.BaseDetector
	maxLOC     int
	maxParams  int
	maxReturns int
	maxNesting int
}

func NewGodFunctionDetector(finder quality.FileFinder) *GodFunctionDetector {
	return &GodFunctionDetector{
		BaseDetector: quality.NewBaseDetector("god_function", quality.SeverityT3, finder),
		maxLOC:       50,
		maxParams:    5,
		maxReturns:   3,
		maxNesting:   4,
	}
}

func (d *GodFunctionDetector) Name() string {
	return "god_function"
}

func (d *GodFunctionDetector) Severity() quality.Severity {
	return quality.SeverityT3
}

func (d *GodFunctionDetector) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	files, err := d.FindFiles(path, "go")
	if err != nil {
		return nil, err
	}

	var findings []quality.Finding
	for _, file := range files {
		fileFindings := d.analyzeFile(file)
		findings = append(findings, fileFindings...)
	}

	return findings, nil
}

func (d *GodFunctionDetector) analyzeFile(path string) []quality.Finding {
	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, path, nil, 0)
	if err != nil {
		return nil
	}

	var findings []quality.Finding
	for _, decl := range node.Decls {
		fn, ok := decl.(*ast.FuncDecl)
		if !ok {
			continue
		}

		startLine := fset.Position(fn.Pos()).Line
		endLine := fset.Position(fn.End()).Line
		loc := endLine - startLine + 1

		paramCount := 0
		if fn.Type.Params != nil {
			for _, field := range fn.Type.Params.List {
				paramCount += len(field.Names)
				if len(field.Names) == 0 {
					paramCount++
				}
			}
		}

		returnCount := 0
		if fn.Type.Results != nil {
			returnCount = len(fn.Type.Results.List)
		}

		nestingDepth := d.calculateNesting(fn)

		var issues []string
		if loc > d.maxLOC {
			issues = append(issues, fmt.Sprintf("%d lines (max %d)", loc, d.maxLOC))
		}
		if paramCount > d.maxParams {
			issues = append(issues, fmt.Sprintf("%d params (max %d)", paramCount, d.maxParams))
		}
		if returnCount > d.maxReturns {
			issues = append(issues, fmt.Sprintf("%d returns (max %d)", returnCount, d.maxReturns))
		}
		if nestingDepth > d.maxNesting {
			issues = append(issues, fmt.Sprintf("nesting depth %d (max %d)", nestingDepth, d.maxNesting))
		}

		if len(issues) > 0 {
			finding := quality.Finding{
				ID:          fmt.Sprintf("god_function::%s::%s", path, fn.Name.Name),
				Type:        "god_function",
				Title:       fmt.Sprintf("God function: %s", fn.Name.Name),
				Description: fmt.Sprintf("Function '%s' has issues: %s", fn.Name.Name, strings.Join(issues, ", ")),
				File:        path,
				Line:        startLine,
				Severity:    quality.SeverityT3,
				Score:       len(issues) * 3,
				Status:      quality.StatusOpen,
				Metadata: map[string]string{
					"function":      fn.Name.Name,
					"loc":           fmt.Sprintf("%d", loc),
					"params":        fmt.Sprintf("%d", paramCount),
					"returns":       fmt.Sprintf("%d", returnCount),
					"nesting_depth": fmt.Sprintf("%d", nestingDepth),
				},
			}
			findings = append(findings, finding)
		}
	}

	return findings
}

func (d *GodFunctionDetector) calculateNesting(fn *ast.FuncDecl) int {
	maxDepth := 0

	var visit func(n ast.Node, depth int)
	visit = func(n ast.Node, depth int) {
		if depth > maxDepth {
			maxDepth = depth
		}

		switch stmt := n.(type) {
		case *ast.IfStmt:
			visit(stmt.Body, depth+1)
			if stmt.Else != nil {
				visit(stmt.Else, depth+1)
			}
		case *ast.ForStmt:
			visit(stmt.Body, depth+1)
		case *ast.RangeStmt:
			visit(stmt.Body, depth+1)
		case *ast.SwitchStmt:
			visit(stmt.Body, depth+1)
		case *ast.SelectStmt:
			visit(stmt.Body, depth+1)
		case *ast.BlockStmt:
			for _, s := range stmt.List {
				visit(s, depth)
			}
		case *ast.CaseClause:
			for _, s := range stmt.Body {
				visit(s, depth)
			}
		}
	}

	if fn.Body != nil {
		visit(fn.Body, 0)
	}

	return maxDepth
}

func exprToString(expr ast.Expr) string {
	switch e := expr.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.SelectorExpr:
		return exprToString(e.X) + "." + e.Sel.Name
	default:
		return ""
	}
}

func countLines(path string) (int, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return 0, err
	}
	return strings.Count(string(data), "\n") + 1, nil
}
