"""Feedback Loop Closer - Detects user corrections and extracts lessons."""

from __future__ import annotations

import json
import re
import uuid
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any


@dataclass
class CorrectionPattern:
    """Pattern for detecting corrections."""

    name: str
    regex: str
    weight: float
    compiled_regex: Any = None

    def __post_init__(self) -> None:
        import re as regex_module

        self.compiled_regex = regex_module.compile(self.regex, regex_module.IGNORECASE)

    def extract_lesson(self, text: str) -> str:
        """Extract lesson from matched text."""
        if self.name == "direct_correction":
            after = re.sub(r"^[^,]+[,]?\s*", "", text)
            return after or text
        return text


@dataclass
class CategoryConfig:
    """Configuration for a category."""

    patterns: list[str]
    target_file: str
    section: str
    compiled_patterns: list[Any] = None

    def __post_init__(self) -> None:
        import re as regex_module

        self.compiled_patterns = [
            regex_module.compile(p, regex_module.IGNORECASE) for p in self.patterns
        ]


# Default correction patterns
DEFAULT_CORRECTION_PATTERNS = [
    CorrectionPattern(
        name="direct_correction",
        regex=r"^(?:no[,]?\s+|actually[,]?\s+|wait[,]?\s+|hold on[,]?\s+|incorrect[,]?\s+|wrong[,]?\s+|that's not right)",
        weight=1.0,
    ),
    CorrectionPattern(
        name="should_have",
        regex=r"(?:you should have|you should've|you should use|you should do|try using|use instead)",
        weight=0.9,
    ),
    CorrectionPattern(
        name="not_correct",
        regex=r"(?:that's not correct|that isn't correct|not quite|not exactly|almost[,]? but)",
        weight=0.85,
    ),
    CorrectionPattern(
        name="alternative",
        regex=r"(?:better to|would be better|preferably|instead of that|rather than)",
        weight=0.8,
    ),
    CorrectionPattern(
        name="missed_point",
        regex=r"(?:you missed|you didn't|you forgot to|what about|don't forget)",
        weight=0.75,
    ),
    CorrectionPattern(
        name="clarification",
        regex=r"(?:i meant|what i meant|to clarify|let me clarify|to be clear)",
        weight=0.7,
    ),
]

# Default category patterns
DEFAULT_CATEGORIES = {
    "tools": CategoryConfig(
        patterns=[r"tool|skill|command|function|use \w+ instead"],
        target_file="TOOLS.md",
        section="Tool Usage Notes",
    ),
    "behavior": CategoryConfig(
        patterns=[r"always|never|should|shouldn't|remember to|don't|stop"],
        target_file="AGENTS.md",
        section="Behavioral Rules",
    ),
    "memory": CategoryConfig(
        patterns=[r"said|told you|mentioned|before|yesterday|last time"],
        target_file="MEMORY.md",
        section="Key Facts",
    ),
    "security": CategoryConfig(
        patterns=[r"secret|password|token|key|credential|auth"],
        target_file="AGENTS.md",
        section="Security Rules",
    ),
}


class FeedbackLoopCloser:
    """Detects user corrections and extracts lessons."""

    def __init__(
        self,
        memory_dir: str | None = None,
        agents_file: str | None = None,
        tools_file: str | None = None,
        pending_file: str | None = None,
        min_confidence: float = 0.7,
    ) -> None:
        from oclawma.config import get_workspace_dir

        _ws = get_workspace_dir()
        self.memory_dir = Path(memory_dir or str(_ws / "memory"))
        self.agents_file = Path(agents_file or str(_ws / "AGENTS.md"))
        self.tools_file = Path(tools_file or str(_ws / "TOOLS.md"))
        self.pending_file = Path(pending_file or str(_ws / "memory" / "pending-corrections.json"))
        self.min_confidence = min_confidence
        self.correction_patterns = DEFAULT_CORRECTION_PATTERNS
        self.categories = DEFAULT_CATEGORIES

    def _init_pending_file(self) -> None:
        """Initialize pending corrections file."""
        if not self.pending_file.exists():
            self.pending_file.parent.mkdir(parents=True, exist_ok=True)
            self._save_pending(
                {"pending": [], "applied": [], "lastUpdated": datetime.now().isoformat()}
            )

    def _load_pending(self) -> dict:
        """Load pending corrections."""
        try:
            with open(self.pending_file, encoding="utf-8") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {"pending": [], "applied": [], "lastUpdated": datetime.now().isoformat()}

    def _save_pending(self, data: dict) -> None:
        """Save pending corrections."""
        data["lastUpdated"] = datetime.now().isoformat()
        with open(self.pending_file, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)

    def detect_correction(self, text: str) -> dict | None:
        """Detect if text contains a correction."""
        matches = []

        for pattern in self.correction_patterns:
            match = pattern.compiled_regex.search(text)
            if match:
                lesson = pattern.extract_lesson(text)
                matches.append(
                    {
                        "pattern": pattern.name,
                        "confidence": pattern.weight,
                        "matchedText": match.group(0),
                        "lesson": lesson,
                        "fullText": text,
                    }
                )

        # Return highest confidence match
        if matches:
            return sorted(matches, key=lambda x: x["confidence"], reverse=True)[0]
        return None

    def categorize_lesson(self, lesson: str, context: str = "") -> dict:
        """Determine category and target file for a lesson."""
        combined = f"{lesson} {context}".lower()

        for category, config in self.categories.items():
            for pattern in config.compiled_patterns:
                if pattern.search(combined):
                    return {
                        "category": category,
                        "targetFile": config.target_file,
                        "section": config.section,
                    }

        # Default to AGENTS.md for behavior rules
        return {
            "category": "behavior",
            "targetFile": "AGENTS.md",
            "section": "Behavioral Rules",
        }

    def generate_rule(self, detection: dict, category: dict) -> dict:
        """Generate a rule from a detected correction."""
        lesson = detection["lesson"]

        # Clean up the lesson text
        rule = re.sub(
            r"^(you should have|you should|try|use|instead)", "", lesson, flags=re.IGNORECASE
        )
        rule = rule.strip()
        rule = re.sub(r"^[,.]\s*", "", rule)

        # Capitalize first letter
        rule = rule[0].upper() + rule[1:] if rule else lesson

        # Add context about what was wrong
        context = detection["matchedText"].rstrip(".,")

        return {
            "rule": rule,
            "context": context,
            "category": category["category"],
            "confidence": detection["confidence"],
        }

    def queue_correction(self, user_message: str, assistant_context: str = "") -> dict | None:
        """Add correction to pending queue."""
        self._init_pending_file()
        data = self._load_pending()

        detection = self.detect_correction(user_message)
        if not detection or detection["confidence"] < self.min_confidence:
            return None

        category = self.categorize_lesson(detection["lesson"], assistant_context)
        rule = self.generate_rule(detection, category)

        correction = {
            "id": self._generate_id(),
            "timestamp": datetime.now().isoformat(),
            "originalMessage": user_message,
            "lesson": detection["lesson"],
            "rule": rule,
            "category": category,
            "confidence": detection["confidence"],
            "status": "pending",
            "applied": False,
        }

        data["pending"].append(correction)
        self._save_pending(data)

        return correction

    def _generate_id(self) -> str:
        """Generate a unique ID."""
        return str(uuid.uuid4())[:8]

    def list_pending(self) -> None:
        """List pending corrections."""
        self._init_pending_file()
        data = self._load_pending()

        print("\n╔══════════════════════════════════════════════════════════╗")
        print("║         PENDING CORRECTIONS                              ║")
        print("╚══════════════════════════════════════════════════════════╝\n")

        if not data["pending"]:
            print("✅ No pending corrections.\n")
            return

        print(f"📋 {len(data['pending'])} correction(s) pending approval:\n")

        for c in data["pending"]:
            icon = "🔥" if c["confidence"] > 0.9 else "✅" if c["confidence"] > 0.8 else "💭"
            print(f"{icon} [{c['id']}] ({int(c['confidence'] * 100)}% confidence)")
            print(f"   Original: \"{c['originalMessage'][:80]}...\"")
            print(f"   Lesson: {c['lesson'][:100]}")
            print(f"   Suggested Rule: {c['rule']['rule']}")
            print(f"   Target: {c['category']['targetFile']} → {c['category']['section']}")
            print()

        print("Commands:")
        print("  oclawma self-improvement feedback apply [id]     - Apply specific correction")
        print("  oclawma self-improvement feedback apply-all      - Apply all pending")
        print("  oclawma self-improvement feedback reject [id]    - Reject correction")
        print()

    def apply_correction(self, correction_id: str) -> None:
        """Apply a correction to the target file."""
        self._init_pending_file()
        data = self._load_pending()

        index = next((i for i, c in enumerate(data["pending"]) if c["id"] == correction_id), -1)

        if index == -1:
            print(f"❌ Correction {correction_id} not found")
            return

        correction = data["pending"][index]

        # Determine target file path
        target_filename = correction["category"]["targetFile"]
        if target_filename == "AGENTS.md":
            target_file = self.agents_file
        elif target_filename == "TOOLS.md":
            target_file = self.tools_file
        else:
            target_file = self.memory_dir.parent / target_filename

        # Format the rule entry
        rule_entry = f"""
### {correction['category']['section']}
**Added:** {datetime.now().isoformat().split('T')[0]}
**From:** "{correction['originalMessage'][:50]}..."

- {correction['rule']['rule']}
"""

        # Append to file
        with open(target_file, "a", encoding="utf-8") as f:
            f.write(rule_entry)

        # Update status
        correction["status"] = "applied"
        correction["appliedAt"] = datetime.now().isoformat()
        data["applied"].append(correction)
        data["pending"].pop(index)
        self._save_pending(data)

        print(f"✅ Applied rule to {target_file}")
        print(f"   Rule: {correction['rule']['rule']}")

    def apply_all(self) -> None:
        """Apply all pending corrections."""
        self._init_pending_file()
        data = self._load_pending()

        if not data["pending"]:
            print("No pending corrections.")
            return

        print(f"Applying {len(data['pending'])} corrections...\n")

        # Make a copy since we'll be modifying the list
        pending_copy = data["pending"][:]
        for correction in pending_copy:
            self.apply_correction(correction["id"])

    def reject_correction(self, correction_id: str) -> None:
        """Reject a correction."""
        self._init_pending_file()
        data = self._load_pending()

        index = next((i for i, c in enumerate(data["pending"]) if c["id"] == correction_id), -1)

        if index == -1:
            print(f"❌ Correction {correction_id} not found")
            return

        data["pending"][index]["status"] = "rejected"
        data["pending"][index]["rejectedAt"] = datetime.now().isoformat()
        data["applied"].append(data["pending"][index])  # Keep for record
        data["pending"].pop(index)
        self._save_pending(data)

        print(f"🗑️ Rejected correction {correction_id}")

    def test_message(self, message: str) -> None:
        """Test a message for correction detection."""
        print(f'\nTesting message: "{message}"')
        print("─" * 50)

        detection = self.detect_correction(message)

        if not detection:
            print("❌ No correction pattern detected")
            return

        print(f"✅ Pattern: {detection['pattern']}")
        print(f"📊 Confidence: {int(detection['confidence'] * 100)}%")
        print(f"📝 Lesson: {detection['lesson']}")

        category = self.categorize_lesson(detection["lesson"])
        rule = self.generate_rule(detection, category)

        print(f"📁 Target: {category['targetFile']} → {category['section']}")
        print(f"⚡ Rule: {rule['rule']}")


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

    closer = FeedbackLoopCloser()
    command = sys.argv[1] if len(sys.argv) > 1 else "list"
    arg = sys.argv[2] if len(sys.argv) > 2 else None

    if command == "list":
        closer.list_pending()
    elif command == "apply":
        if arg:
            closer.apply_correction(arg)
        else:
            print("Usage: apply [id]")
    elif command == "apply-all":
        closer.apply_all()
    elif command == "reject":
        if arg:
            closer.reject_correction(arg)
        else:
            print("Usage: reject [id]")
    elif command == "test":
        if arg:
            closer.test_message(arg)
        else:
            print('Usage: test "message to analyze"')
    elif command == "queue":
        if arg:
            result = closer.queue_correction(arg)
            if result:
                print("✅ Queued correction:")
                print(f"   ID: {result['id']}")
                print(f"   Rule: {result['rule']['rule']}")
            else:
                print("❌ No correction pattern detected")
        else:
            print('Usage: queue "message"')
    else:
        print("Usage:")
        print("  list                    - Show pending corrections")
        print("  apply [id]              - Apply specific correction")
        print("  apply-all               - Apply all pending")
        print("  reject [id]             - Reject correction")
        print('  test "message"          - Test message analysis')
        print('  queue "message"         - Manually queue a correction')


if __name__ == "__main__":
    main()
