package analyzers

import (
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"strings"

	"github.com/yourorg/devour/internal/quality"
	"golang.org/x/tools/go/packages"
)

type ControlFlowAnalyzer struct {
	maxComplexity     int
	maxNesting        int
	maxFunctionLength int
}

func NewControlFlowAnalyzer() *ControlFlowAnalyzer {
	return &ControlFlowAnalyzer{
		maxComplexity:     15,
		maxNesting:        4,
		maxFunctionLength: 50,
	}
}

func (a *ControlFlowAnalyzer) Name() string {
	return "controlflow"
}

func (a *ControlFlowAnalyzer) Severity() quality.Severity {
	return quality.SeverityT3
}

func (a *ControlFlowAnalyzer) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	cfg := &packages.Config{
		Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax,
		Dir:  path,
	}

	pkgs, err := packages.Load(cfg, "./...")
	if err != nil {
		return nil, fmt.Errorf("failed to load packages: %w", err)
	}

	var findings []quality.Finding

	for _, pkg := range pkgs {
		for _, file := range pkg.Syntax {
			pos := pkg.Fset.Position(file.Pos())
			findings = append(findings, a.analyzeFile(pkg.Fset, file, pos.Filename)...)
		}
	}

	return findings, nil
}

func (a *ControlFlowAnalyzer) analyzeFile(fset *token.FileSet, file *ast.File, filename string) []quality.Finding {
	var findings []quality.Finding

	ast.Inspect(file, func(n ast.Node) bool {
		switch node := n.(type) {
		case *ast.FuncDecl:
			findings = append(findings, a.analyzeFunction(fset, node, filename)...)
		case *ast.IfStmt:
			findings = append(findings, a.checkUnreachableCode(fset, node, filename)...)
		case *ast.SwitchStmt:
			findings = append(findings, a.analyzeSwitch(fset, node, filename)...)
		case *ast.ForStmt:
			findings = append(findings, a.analyzeLoop(fset, node, filename)...)
		case *ast.RangeStmt:
			findings = append(findings, a.analyzeRange(fset, node, filename)...)
		}
		return true
	})

	return findings
}

func (a *ControlFlowAnalyzer) analyzeFunction(fset *token.FileSet, fn *ast.FuncDecl, filename string) []quality.Finding {
	var findings []quality.Finding

	complexity := a.calculateCyclomaticComplexity(fn.Body)
	startPos := fset.Position(fn.Pos())
	endPos := fset.Position(fn.End())
	loc := endPos.Line - startPos.Line + 1

	if complexity > a.maxComplexity {
		severity := quality.SeverityT3
		score := complexity - a.maxComplexity
		if complexity > a.maxComplexity*2 {
			severity = quality.SeverityT4
			score = (complexity - a.maxComplexity) * 2
		}

		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("cyclomatic-complexity::%s::%d", filename, startPos.Line),
			Type:        "complexity",
			Title:       fmt.Sprintf("High cyclomatic complexity in %s", fn.Name.Name),
			Description: fmt.Sprintf("Function '%s' has cyclomatic complexity of %d (max: %d). Consider breaking it into smaller functions.", fn.Name.Name, complexity, a.maxComplexity),
			File:        filename,
			Line:        startPos.Line,
			EndLine:     endPos.Line,
			Severity:    severity,
			Score:       score,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"function":   fn.Name.Name,
				"complexity": fmt.Sprintf("%d", complexity),
				"max":        fmt.Sprintf("%d", a.maxComplexity),
			},
		})
	}

	if loc > a.maxFunctionLength {
		severity := quality.SeverityT2
		if loc > a.maxFunctionLength*2 {
			severity = quality.SeverityT3
		}

		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("function-length::%s::%d", filename, startPos.Line),
			Type:        "complexity",
			Title:       fmt.Sprintf("Function too long: %s", fn.Name.Name),
			Description: fmt.Sprintf("Function '%s' is %d lines (max: %d). Consider breaking it into smaller functions.", fn.Name.Name, loc, a.maxFunctionLength),
			File:        filename,
			Line:        startPos.Line,
			Severity:    severity,
			Score:       (loc - a.maxFunctionLength) / 10,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"function": fn.Name.Name,
				"loc":      fmt.Sprintf("%d", loc),
				"max":      fmt.Sprintf("%d", a.maxFunctionLength),
			},
		})
	}

	maxNesting := a.calculateMaxNesting(fn.Body)
	if maxNesting > a.maxNesting {
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("deep-nesting::%s::%d", filename, startPos.Line),
			Type:        "complexity",
			Title:       fmt.Sprintf("Deep nesting in %s", fn.Name.Name),
			Description: fmt.Sprintf("Function '%s' has nesting depth of %d (max: %d). Extract nested code into separate functions.", fn.Name.Name, maxNesting, a.maxNesting),
			File:        filename,
			Line:        startPos.Line,
			Severity:    quality.SeverityT3,
			Score:       maxNesting - a.maxNesting,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"function": fn.Name.Name,
				"nesting":  fmt.Sprintf("%d", maxNesting),
			},
		})
	}

	findings = append(findings, a.checkEarlyReturn(fset, fn, filename)...)

	return findings
}

func (a *ControlFlowAnalyzer) calculateCyclomaticComplexity(node ast.Node) int {
	complexity := 1

	ast.Inspect(node, func(n ast.Node) bool {
		switch n := n.(type) {
		case *ast.IfStmt:
			complexity++
		case *ast.ForStmt:
			complexity++
		case *ast.RangeStmt:
			complexity++
		case *ast.CaseClause:
			complexity++
		case *ast.BinaryExpr:
			if n.Op == token.LAND || n.Op == token.LOR {
				complexity++
			}
		}
		return true
	})

	return complexity
}

func (a *ControlFlowAnalyzer) calculateMaxNesting(node ast.Node) int {
	return a.nestingDepth(node, 0)
}

func (a *ControlFlowAnalyzer) nestingDepth(node ast.Node, current int) int {
	maxDepth := current

	ast.Inspect(node, func(n ast.Node) bool {
		var childNode ast.Node

		switch stmt := n.(type) {
		case *ast.IfStmt:
			childNode = stmt.Body
		case *ast.ForStmt:
			childNode = stmt.Body
		case *ast.RangeStmt:
			childNode = stmt.Body
		case *ast.SelectStmt:
			childNode = stmt.Body
		case *ast.SwitchStmt:
			childNode = stmt.Body
		case *ast.TypeSwitchStmt:
			childNode = stmt.Body
		case *ast.BlockStmt:
			childNode = nil
		default:
			return true
		}

		if childNode != nil {
			depth := a.nestingDepth(childNode, current+1)
			if depth > maxDepth {
				maxDepth = depth
			}
		}
		return true
	})

	return maxDepth
}

func (a *ControlFlowAnalyzer) checkEarlyReturn(fset *token.FileSet, fn *ast.FuncDecl, filename string) []quality.Finding {
	var findings []quality.Finding

	if fn.Body == nil || len(fn.Body.List) < 2 {
		return findings
	}

	ifStmt, ok := fn.Body.List[0].(*ast.IfStmt)
	if !ok || ifStmt.Else == nil {
		return findings
	}

	if _, ok := ifStmt.Else.(*ast.BlockStmt); ok && len(fn.Body.List) > 1 {
		startPos := fset.Position(ifStmt.Pos())
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("early-return::%s::%d", filename, startPos.Line),
			Type:        "quality",
			Title:       fmt.Sprintf("Use early return pattern in %s", fn.Name.Name),
			Description: "Consider using early return instead of if-else to reduce nesting and improve readability.",
			File:        filename,
			Line:        startPos.Line,
			Severity:    quality.SeverityT1,
			Score:       1,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"function": fn.Name.Name,
			},
		})
	}

	return findings
}

func (a *ControlFlowAnalyzer) checkUnreachableCode(fset *token.FileSet, stmt *ast.IfStmt, filename string) []quality.Finding {
	var findings []quality.Finding

	a.checkUnreachableInBranch(fset, stmt.Body, filename, &findings)
	if stmt.Else != nil {
		if elseBlock, ok := stmt.Else.(*ast.BlockStmt); ok {
			a.checkUnreachableInBranch(fset, elseBlock, filename, &findings)
		}
	}

	return findings
}

func (a *ControlFlowAnalyzer) checkUnreachableInBranch(fset *token.FileSet, block *ast.BlockStmt, filename string, findings *[]quality.Finding) {
	hasReturn := false
	for _, stmt := range block.List {
		if hasReturn {
			pos := fset.Position(stmt.Pos())
			*findings = append(*findings, quality.Finding{
				ID:          fmt.Sprintf("unreachable::%s::%d", filename, pos.Line),
				Type:        "dead_code",
				Title:       "Unreachable code after return",
				Description: "Code after return statement will never be executed.",
				File:        filename,
				Line:        pos.Line,
				Severity:    quality.SeverityT2,
				Score:       3,
				Status:      quality.StatusOpen,
			})
			break
		}
		if _, ok := stmt.(*ast.ReturnStmt); ok {
			hasReturn = true
		}
	}
}

func (a *ControlFlowAnalyzer) analyzeSwitch(fset *token.FileSet, stmt *ast.SwitchStmt, filename string) []quality.Finding {
	var findings []quality.Finding

	pos := fset.Position(stmt.Pos())

	hasDefault := false
	caseCount := 0

	for _, s := range stmt.Body.List {
		if clause, ok := s.(*ast.CaseClause); ok {
			caseCount++
			if clause.List == nil {
				hasDefault = true
			}
		}
	}

	if !hasDefault && caseCount > 0 {
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("switch-no-default::%s::%d", filename, pos.Line),
			Type:        "quality",
			Title:       "Switch without default case",
			Description: "Switch statement lacks a default case. Consider handling unexpected values explicitly.",
			File:        filename,
			Line:        pos.Line,
			Severity:    quality.SeverityT1,
			Score:       1,
			Status:      quality.StatusOpen,
		})
	}

	if caseCount > 10 {
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("switch-too-many-cases::%s::%d", filename, pos.Line),
			Type:        "complexity",
			Title:       "Switch with too many cases",
			Description: fmt.Sprintf("Switch has %d cases. Consider using a map or polymorphism instead.", caseCount),
			File:        filename,
			Line:        pos.Line,
			Severity:    quality.SeverityT2,
			Score:       caseCount / 5,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"case_count": fmt.Sprintf("%d", caseCount),
			},
		})
	}

	return findings
}

func (a *ControlFlowAnalyzer) analyzeLoop(fset *token.FileSet, stmt *ast.ForStmt, filename string) []quality.Finding {
	var findings []quality.Finding

	pos := fset.Position(stmt.Pos())

	if stmt.Cond == nil && stmt.Post == nil {
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("infinite-loop::%s::%d", filename, pos.Line),
			Type:        "quality",
			Title:       "Potential infinite loop",
			Description: "For loop has no condition and no post statement. Ensure there's a break inside.",
			File:        filename,
			Line:        pos.Line,
			Severity:    quality.SeverityT3,
			Score:       4,
			Status:      quality.StatusOpen,
		})
	}

	if strings.Contains(fmt.Sprintf("%v", stmt.Cond), "== true") {
		findings = append(findings, quality.Finding{
			ID:          fmt.Sprintf("redundant-bool-compare::%s::%d", filename, pos.Line),
			Type:        "quality",
			Title:       "Redundant boolean comparison",
			Description: "Comparing to 'true' is redundant. Use the boolean value directly.",
			File:        filename,
			Line:        pos.Line,
			Severity:    quality.SeverityT1,
			Score:       1,
			Status:      quality.StatusOpen,
		})
	}

	return findings
}

func (a *ControlFlowAnalyzer) analyzeRange(fset *token.FileSet, stmt *ast.RangeStmt, filename string) []quality.Finding {
	var findings []quality.Finding

	pos := fset.Position(stmt.Pos())

	if stmt.Key != nil {
		if ident, ok := stmt.Key.(*ast.Ident); ok && ident.Name == "_" {
		} else if stmt.Body != nil {
			used := false
			keyName := ""
			if ident, ok := stmt.Key.(*ast.Ident); ok {
				keyName = ident.Name
			}
			ast.Inspect(stmt.Body, func(n ast.Node) bool {
				if ident, ok := n.(*ast.Ident); ok && ident.Name == keyName {
					used = true
				}
				return true
			})
			if !used && keyName != "" {
				findings = append(findings, quality.Finding{
					ID:          fmt.Sprintf("unused-range-key::%s::%d", filename, pos.Line),
					Type:        "quality",
					Title:       "Unused range key",
					Description: fmt.Sprintf("Range key '%s' is not used. Use '_' to ignore it explicitly.", keyName),
					File:        filename,
					Line:        pos.Line,
					Severity:    quality.SeverityT1,
					Score:       1,
					Status:      quality.StatusOpen,
					Metadata: map[string]string{
						"variable": keyName,
					},
				})
			}
		}
	}

	return findings
}
