package detectors

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

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

// ComplexityDetector detects complexity issues in source code
type ComplexityDetector struct {
	*quality.BaseDetector
	signals []ComplexitySignal
}

// ComplexitySignal represents a complexity pattern to detect
type ComplexitySignal struct {
	Name      string
	Pattern   *regexp.Regexp
	Weight    int
	Threshold int
	Compute   func(content string, lines []string) (int, string)
}

// NewComplexityDetector creates a new complexity detector
func NewComplexityDetector(finder quality.FileFinder) *ComplexityDetector {
	detector := &ComplexityDetector{
		BaseDetector: quality.NewBaseDetector("complexity", quality.SeverityT2, finder),
		signals: []ComplexitySignal{
			{
				Name:      "nested if statements",
				Pattern:   regexp.MustCompile(`^\s*if\s+.*\{\s*$`),
				Weight:    2,
				Threshold: 3,
			},
			{
				Name:      "nested for loops",
				Pattern:   regexp.MustCompile(`^\s*for\s+.*\{\s*$`),
				Weight:    3,
				Threshold: 2,
			},
			{
				Name:      "switch statements",
				Pattern:   regexp.MustCompile(`^\s*switch\s+.*\{\s*$`),
				Weight:    1,
				Threshold: 5,
			},
			{
				Name:      "function calls",
				Pattern:   regexp.MustCompile(`\w+\(`),
				Weight:    1,
				Threshold: 20,
			},
		},
	}

	// Add Go-specific complexity signals
	detector.addGoSignals()

	return detector
}

// addGoSignals adds Go-specific complexity signals
func (d *ComplexityDetector) addGoSignals() {
	goSignals := []ComplexitySignal{
		{
			Name:      "goroutines",
			Pattern:   regexp.MustCompile(`go\s+\w+\(`),
			Weight:    2,
			Threshold: 3,
		},
		{
			Name:      "channels",
			Pattern:   regexp.MustCompile(`make\s*\(\s*chan`),
			Weight:    2,
			Threshold: 3,
		},
		{
			Name:      "select statements",
			Pattern:   regexp.MustCompile(`^\s*select\s*\{`),
			Weight:    3,
			Threshold: 2,
		},
		{
			Name:      "defer statements",
			Pattern:   regexp.MustCompile(`^\s*defer\s+`),
			Weight:    1,
			Threshold: 5,
		},
	}

	d.signals = append(d.signals, goSignals...)
}

// Name returns the detector name
func (d *ComplexityDetector) Name() string {
	return "complexity"
}

// Severity returns the default severity
func (d *ComplexityDetector) Severity() quality.Severity {
	return quality.SeverityT2
}

// Detect runs complexity detection on the given path
func (d *ComplexityDetector) Detect(ctx context.Context, path string, config *quality.Config) ([]quality.Finding, error) {
	files, err := d.FindFiles(path, config.Language)
	if err != nil {
		return nil, fmt.Errorf("failed to find files: %w", err)
	}

	var findings []quality.Finding

	for _, file := range files {
		if quality.ShouldExclude(file, config.Exclude) {
			continue
		}

		fileFindings, err := d.analyzeFile(file, config)
		if err != nil {
			log.Printf("Failed to analyze file %s: %v", file, err)
			continue
		}

		findings = append(findings, fileFindings...)
	}

	return findings, nil
}

// analyzeFile analyzes a single file for complexity issues
func (d *ComplexityDetector) analyzeFile(filePath string, config *quality.Config) ([]quality.Finding, error) {
	content, err := filepath.Abs(filePath)
	if err != nil {
		return nil, err
	}

	// Read file content
	fileContent, err := os.ReadFile(content)
	if err != nil {
		return nil, err
	}

	contentStr := string(fileContent)
	lines := strings.Split(contentStr, "\n")
	loc := len(lines)

	if loc < config.MinLOC {
		return nil, nil
	}

	var findings []quality.Finding
	score := 0
	var signals []string

	// Check each complexity signal
	for _, signal := range d.signals {
		var count int
		var label string

		if signal.Compute != nil {
			c, l := signal.Compute(contentStr, lines)
			if c > 0 {
				count = c
				label = l
			}
		} else if signal.Pattern != nil {
			matches := signal.Pattern.FindAllString(contentStr, -1)
			count = len(matches)
			if count > signal.Threshold {
				label = fmt.Sprintf("%d %s", count, signal.Name)
			}
		}

		if count > signal.Threshold {
			signals = append(signals, label)
			excess := count - signal.Threshold
			if signal.Threshold == 0 {
				excess = count
			}
			score += excess * signal.Weight
		}
	}

	// Create finding if score exceeds threshold
	if score >= config.Threshold && len(signals) > 0 {
		finding := quality.Finding{
			ID:          fmt.Sprintf("complexity-%s-%d", filepath.Base(filePath), score),
			Type:        "complexity",
			Title:       "High complexity detected",
			Description: fmt.Sprintf("File has complexity score of %d with signals: %s", score, strings.Join(signals, ", ")),
			File:        filePath,
			Line:        1,
			Severity:    d.Severity(),
			Score:       score,
			Status:      quality.StatusOpen,
			Metadata: map[string]string{
				"loc":     strconv.Itoa(loc),
				"signals": strings.Join(signals, ";"),
			},
		}
		findings = append(findings, finding)
	}

	return findings, nil
}
