"""Error Pattern Detector - Analyzes logs to identify recurring error patterns."""

from __future__ import annotations

import json
import os
import re
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any


@dataclass
class ErrorPattern:
    """Definition of an error pattern."""

    name: str
    regex: str
    category: str
    severity: str
    suggestion: str
    compiled_regex: Any = field(repr=False, default=None)

    def __post_init__(self) -> None:
        self.compiled_regex = re.compile(self.regex, re.IGNORECASE)


@dataclass
class ErrorOccurrence:
    """A single error occurrence."""

    pattern: str
    category: str
    severity: str
    suggestion: str
    line: str
    context: str
    timestamp: str
    file: str


# Default error patterns
DEFAULT_PATTERNS = [
    ErrorPattern(
        name="file_not_found",
        regex=r"(?:ENOENT|no such file|File not found|Cannot find module)",
        category="file_access",
        severity="medium",
        suggestion="Verify file paths exist before operations. Check for typos.",
    ),
    ErrorPattern(
        name="permission_denied",
        regex=r"(?:EACCES|permission denied|Permission denied)",
        category="permissions",
        severity="high",
        suggestion="Check file permissions. Use sudo only when necessary.",
    ),
    ErrorPattern(
        name="syntax_error",
        regex=r"(?:SyntaxError|Unexpected token|Invalid syntax)",
        category="syntax",
        severity="high",
        suggestion="Review code syntax. Check for missing brackets/quotes.",
    ),
    ErrorPattern(
        name="command_not_found",
        regex=r"(?:command not found|not installed|is not recognized)",
        category="tool_usage",
        severity="medium",
        suggestion="Verify tool is installed and in PATH.",
    ),
    ErrorPattern(
        name="api_error",
        regex=r"(?:API error|Request failed|HTTP \d{3}|timeout|ECONNREFUSED)",
        category="api",
        severity="medium",
        suggestion="Check network connectivity and API status.",
    ),
    ErrorPattern(
        name="wrong_tool",
        regex=r"(?:wrong tool|should use|instead of|you should)",
        category="tool_selection",
        severity="low",
        suggestion="Review tool capabilities before selection.",
    ),
    ErrorPattern(
        name="wrong_path",
        regex=r"(?:wrong path|incorrect path|typo in path)",
        category="file_access",
        severity="low",
        suggestion="Double-check file paths for typos.",
    ),
    ErrorPattern(
        name="missing_scope",
        regex=r"(?:missing scope|missing scopes|unauthorized|authentication)",
        category="auth",
        severity="high",
        suggestion="Check API credentials and required scopes.",
    ),
]


class ErrorPatternDetector:
    """Detects and tracks error patterns in logs."""

    def __init__(
        self,
        session_log_dir: str | None = None,
        patterns_file: str | None = None,
        rules_file: str | None = None,
        min_occurrences: int = 2,
        lookback_days: int = 7,
    ) -> None:
        from oclawma.config import get_config_dir, get_workspace_dir

        self.session_log_dir = Path(
            session_log_dir or os.getenv("OPENCLAW_LOGS", str(get_config_dir() / "logs"))
        )
        self.patterns_file = Path(
            patterns_file or str(get_workspace_dir() / "memory" / "error-patterns.json")
        )
        self.rules_file = Path(rules_file or str(get_workspace_dir() / "AGENTS.md"))
        self.min_occurrences = min_occurrences
        self.lookback_days = lookback_days
        self.patterns = DEFAULT_PATTERNS

    def _init_patterns_file(self) -> None:
        """Initialize patterns file if it doesn't exist."""
        if not self.patterns_file.exists():
            self.patterns_file.parent.mkdir(parents=True, exist_ok=True)
            self._save_patterns(
                {"patterns": {}, "lastUpdated": datetime.now().isoformat(), "totalErrors": 0}
            )

    def _load_patterns(self) -> dict:
        """Load existing patterns."""
        try:
            with open(self.patterns_file, encoding="utf-8") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {
                "patterns": {},
                "lastUpdated": datetime.now().isoformat(),
                "totalErrors": 0,
            }

    def _save_patterns(self, data: dict) -> None:
        """Save patterns to file."""
        data["lastUpdated"] = datetime.now().isoformat()
        with open(self.patterns_file, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)

    def _find_log_files(self) -> list[Path]:
        """Find log files within lookback period."""
        files = []
        cutoff = datetime.now() - timedelta(days=self.lookback_days)

        if self.session_log_dir.exists():
            for entry in self.session_log_dir.iterdir():
                if entry.is_file() and datetime.fromtimestamp(entry.stat().st_mtime) > cutoff:
                    files.append(entry)

        return files

    def _extract_timestamp(self, line: str) -> str | None:
        """Extract timestamp from log line."""
        match = re.search(r"\[(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2})", line)
        return match.group(1) if match else None

    def _analyze_log_file(self, file_path: Path) -> list[ErrorOccurrence]:
        """Analyze a single log file for errors."""
        errors = []

        try:
            with open(file_path, encoding="utf-8", errors="ignore") as f:
                lines = f.readlines()
        except Exception:
            return errors

        for i, line in enumerate(lines):
            for pattern in self.patterns:
                if pattern.compiled_regex.search(line):
                    # Extract context (3 lines before and after)
                    context_start = max(0, i - 3)
                    context_end = min(len(lines), i + 4)
                    context = "".join(lines[context_start:context_end])

                    errors.append(
                        ErrorOccurrence(
                            pattern=pattern.name,
                            category=pattern.category,
                            severity=pattern.severity,
                            suggestion=pattern.suggestion,
                            line=line.strip()[:200],  # Truncate long lines
                            context=context[:500],
                            timestamp=self._extract_timestamp(line) or datetime.now().isoformat(),
                            file=file_path.name,
                        )
                    )

        return errors

    def analyze(self) -> dict:
        """Analyze all logs and update patterns."""
        self._init_patterns_file()
        patterns = self._load_patterns()
        log_files = self._find_log_files()

        print(f"📁 Found {len(log_files)} log files to analyze")

        new_errors = 0

        for file_path in log_files:
            errors = self._analyze_log_file(file_path)
            new_errors += len(errors)

            for error in errors:
                if error.pattern not in patterns["patterns"]:
                    patterns["patterns"][error.pattern] = {
                        "name": error.pattern,
                        "category": error.category,
                        "severity": error.severity,
                        "suggestion": error.suggestion,
                        "occurrences": [],
                        "firstSeen": error.timestamp,
                        "count": 0,
                    }

                # Avoid duplicate entries from same file
                existing = any(
                    o.get("file") == error.file and o.get("line") == error.line
                    for o in patterns["patterns"][error.pattern]["occurrences"]
                )

                if not existing:
                    patterns["patterns"][error.pattern]["occurrences"].append(
                        {
                            "timestamp": error.timestamp,
                            "file": error.file,
                            "line": error.line,
                            "context": error.context,
                        }
                    )
                    patterns["patterns"][error.pattern]["count"] += 1
                    patterns["totalErrors"] += 1

        self._save_patterns(patterns)
        print(f"✅ Analysis complete. Found {new_errors} errors.")
        print(f"📊 Total patterns tracked: {len(patterns['patterns'])}")

        return patterns

    def generate_report(self) -> None:
        """Generate a report of error patterns."""
        patterns = self._load_patterns()
        entries = sorted(
            patterns["patterns"].values(), key=lambda x: x.get("count", 0), reverse=True
        )

        print("\n╔══════════════════════════════════════════════════════════╗")
        print("║         ERROR PATTERN REPORT                             ║")
        print("╚══════════════════════════════════════════════════════════╝\n")

        print(f"📅 Report generated: {datetime.now().isoformat()}")
        print(f"📊 Total errors tracked: {patterns.get('totalErrors', 0)}")
        print(f"🔍 Unique patterns: {len(entries)}\n")

        # Group by severity
        by_severity: dict[str, list[dict]] = {"high": [], "medium": [], "low": []}
        for entry in entries:
            severity = entry.get("severity", "medium")
            by_severity.setdefault(severity, []).append(entry)

        # Show high severity first
        if by_severity.get("high"):
            print("🔴 HIGH SEVERITY PATTERNS\n" + "═" * 50)
            for p in by_severity["high"]:
                self._print_pattern(p)

        if by_severity.get("medium"):
            print("\n🟡 MEDIUM SEVERITY PATTERNS\n" + "═" * 50)
            for p in by_severity["medium"]:
                self._print_pattern(p)

        if by_severity.get("low"):
            print("\n🟢 LOW SEVERITY PATTERNS\n" + "═" * 50)
            for p in by_severity["low"]:
                self._print_pattern(p)

        # Recommendations
        recurring = [e for e in entries if e.get("count", 0) >= self.min_occurrences]
        if recurring:
            print("\n\n💡 RECOMMENDED ACTIONS\n" + "═" * 50)
            for p in recurring:
                print(f"\n• {p['name']} ({p['count']}x)")
                print(f"  → {p['suggestion']}")

        print()

    def _print_pattern(self, p: dict) -> None:
        """Print a single pattern."""
        count = p.get("count", 0)
        icon = "🔥" if count >= 5 else "⚠️" if count >= 3 else "•"
        first_seen = p.get("firstSeen", "unknown")
        if first_seen != "unknown":
            first_seen = first_seen.split("T")[0]

        print(f"\n{icon} {p['name'].upper()}")
        print(f"   Category: {p['category']} | Count: {count} | First: {first_seen}")
        print(f"   💡 {p['suggestion']}")
        if count >= self.min_occurrences:
            print("   📝 Should add to AGENTS.md")

    def _generate_rule(self, pattern: dict) -> str:
        """Generate a rule from a pattern."""
        rules = {
            "file_not_found": "Always verify file existence with `test -f` or `ls` before operations",
            "permission_denied": "Check permissions with `ls -la` before modifying files",
            "syntax_error": "Validate syntax before executing code changes",
            "command_not_found": "Verify tool installation before use",
            "api_error": "Check API status and credentials before calls",
            "wrong_tool": "Review tool capabilities before selection",
            "wrong_path": "Double-check all paths for typos",
            "missing_scope": "Verify API scopes and credentials are configured",
        }
        return rules.get(pattern["name"], pattern.get("suggestion", ""))

    def suggest_rules(self) -> None:
        """Suggest rules based on recurring patterns."""
        patterns = self._load_patterns()
        entries = sorted(
            [p for p in patterns["patterns"].values() if p.get("count", 0) >= self.min_occurrences],
            key=lambda x: x.get("count", 0),
            reverse=True,
        )

        if not entries:
            print("✅ No recurring patterns found that need rules.")
            return

        print("\n📋 SUGGESTED RULES FOR AGENTS.md\n" + "═" * 50)

        for p in entries:
            rule = self._generate_rule(p)
            print(f"\n## {p['category'].upper()}: {p['name']}")
            print(f"- **Issue**: {p['name'].replace('_', ' ')} ({p['count']} occurrences)")
            print(f"- **Rule**: {rule}")

        print("\n\nTo add these rules automatically, run:")
        print("  oclawma self-improvement apply-rules")

    def apply_rules(self) -> None:
        """Apply suggested rules to AGENTS.md."""
        patterns = self._load_patterns()
        entries = [
            p for p in patterns["patterns"].values() if p.get("count", 0) >= self.min_occurrences
        ]

        if not entries:
            print("No rules to apply.")
            return

        rules_section = "\n\n## Auto-Generated Rules (from Error Pattern Detector)\n\n"
        rules_section += f"_Generated: {datetime.now().isoformat()}_\n\n"

        for p in entries:
            rules_section += f"### {p['category'].upper()}: {p['name'].replace('_', ' ')}\n"
            rules_section += f"- **Occurrences**: {p['count']}\n"
            rules_section += f"- **Rule**: {self._generate_rule(p)}\n\n"

        with open(self.rules_file, "a", encoding="utf-8") as f:
            f.write(rules_section)

        print(f"✅ Appended {len(entries)} rules to {self.rules_file}")


def main() -> None:
    """CLI entry point."""
    import sys

    detector = ErrorPatternDetector()
    command = sys.argv[1] if len(sys.argv) > 1 else "analyze"

    if command == "analyze":
        detector.analyze()
    elif command == "report":
        detector.generate_report()
    elif command == "suggest":
        detector.suggest_rules()
    elif command == "apply-rules":
        detector.apply_rules()
    else:
        print("Usage: error-pattern-detector [analyze|report|suggest|apply-rules]")
        sys.exit(1)


if __name__ == "__main__":
    main()
