#!/usr/bin/env python3
"""
Correction Capture Engine

Detects correction patterns in messages and creates draft corrections.
Uses a two-stage approach:
1. Fast regex pre-filter to find candidates
2. Semantic LLM classification for accurate detection

The semantic classifier distinguishes:
- CORRECTION: User indicating something was WRONG (log it)
- PREFERENCE: User wants something different (skip)
- NEW_INFO: Information you couldn't have known (skip)
- ITERATION: Exploring options (skip)
"""

import re
import json
import sys
from datetime import datetime
from pathlib import Path

# Import semantic classifier
try:
    from semantic_classifier import classify_message, ClassificationResult
    HAS_SEMANTIC = True
except ImportError:
    HAS_SEMANTIC = False

# Optional yaml support
try:
    import yaml
    HAS_YAML = True
except ImportError:
    HAS_YAML = False

CORRECTIONS_DIR = Path("/home/aria/.openclaw/workspace/agency/corrections")
INDEX_FILE = CORRECTIONS_DIR / "_index.json"

# Pre-filter patterns (quick scan to find candidates for semantic analysis)
CORRECTION_PATTERNS = [
    # Explicit corrections
    (r"no[,.]?\s+you\s+should", "explicit", 0.9),
    (r"no[,.]?\s+that'?s?\s+wrong", "explicit", 0.95),
    (r"^that'?s?\s+wrong", "explicit", 0.90),  # Standalone "that's wrong"
    (r"actually[,.]?\s+you\s+need\s+to", "explicit", 0.85),
    (r"i\s+told\s+you\s+before", "explicit", 0.9),
    (r"don'?t\s+do\s+that", "explicit", 0.8),
    (r"don'?t\s+do\s+\w+\s+like\s+that", "explicit", 0.8),  # "don't do X like that"
    (r"double\s+check\s+(?:everything|before|first)", "teaching", 0.75),  # "double check before"
    (r"verify\s+(?:everything|before|first)", "teaching", 0.75),  # "verify before"
    (r"before\s+(?:sounding|raising|posting|sending)\s+(?:the\s+)?alarm", "teaching", 0.8),  # "before sounding the alarm"
    (r"that'?s?\s+not\s+how", "explicit", 0.85),
    (r"wrong\s+approach", "explicit", 0.9),
    (r"you\s+should\s+have", "explicit", 0.75),
    (r"the\s+correct\s+(?:way|approach)\s+is", "explicit", 0.85),  # Teaching pattern
    (r"never\s+(?:do|use|deploy|send|share)", "explicit", 0.85),  # Prohibition
    (r"always\s+(?:do|use|make\s+sure)", "explicit", 0.80),  # Requirement
    (r"the\s+rule\s+is", "explicit", 0.80),  # Rule statement
    (r"here'?s?\s+how\s+(?:we|you)\s+(?:do|should)", "teaching", 0.75),  # Teaching
    
    # Softer corrections
    (r"next\s+time[,.]?\s+(?:please\s+)?(?:make\s+sure|remember)", "soft", 0.7),
    (r"(?:please\s+)?don'?t\s+forget", "soft", 0.6),
    (r"that'?s?\s+not\s+(?:right|correct)", "soft", 0.85),
    (r"(?:stop|quit)\s+doing", "explicit", 0.85),
    
    # Revert signals
    (r"revert\s+(?:this|that|it)", "revert", 0.95),
    (r"undo\s+(?:this|that|it|what)", "revert", 0.9),
    (r"roll\s*back", "revert", 0.9),
    
    # Frustration signals (lower confidence, needs context)
    (r"why\s+(?:did|do)\s+you", "frustration", 0.5),
    (r"i\s+(?:already|just)\s+(?:told|said)", "frustration", 0.7),
]

# Authority mapping
AUTHORITY_MAP = {
    "coderaven": ("raven", "cto"),
    "raven": ("raven", "cto"),
    "jarrhey": ("jarrhey", "lead"),
    "jim.paano": ("jim", "lead"),
    "jim": ("jim", "lead"),
    "gee": ("gee", "lead"),
    "dan": ("dan", "lead"),
    "dave": ("dave", "lead"),
    "daveove": ("dave", "lead"),
    "nickg_06192": ("nick", "lead"),
}

def get_next_correction_id():
    """Get the next available COR-XXX id."""
    if not INDEX_FILE.exists():
        return "COR-022"
    
    with open(INDEX_FILE) as f:
        index = json.load(f)
    
    existing_ids = [c["id"] for c in index.get("corrections", [])]
    max_num = 0
    for cid in existing_ids:
        if cid.startswith("COR-"):
            try:
                num = int(cid.split("-")[1])
                max_num = max(max_num, num)
            except:
                pass
    return f"COR-{max_num + 1:03d}"


def detect_correction_regex(message: str) -> tuple[bool, str, float]:
    """
    Fast regex pre-filter to identify correction candidates.
    Returns: (is_candidate, pattern_type, confidence)
    
    Note: This is a PRE-FILTER, not the final decision.
    Use detect_correction() for actual classification.
    """
    message_lower = message.lower()
    
    for pattern, ptype, confidence in CORRECTION_PATTERNS:
        if re.search(pattern, message_lower):
            return True, ptype, confidence
    
    return False, "", 0.0


def detect_correction(
    message: str,
    context: str = "",
    use_semantic: bool = True
) -> tuple[bool, str, float, dict]:
    """
    Detect if a message contains a correction.
    
    Uses two-stage approach:
    1. Quick regex pre-filter
    2. Semantic LLM classification (if available)
    
    Returns: (is_correction, category, confidence, details)
    """
    # Stage 1: Quick regex pre-filter
    is_candidate, regex_type, regex_confidence = detect_correction_regex(message)
    
    # If no regex match and semantic disabled, skip
    if not is_candidate and not use_semantic:
        return False, "", 0.0, {}
    
    # Stage 2: Semantic classification (if available and enabled)
    if HAS_SEMANTIC and use_semantic:
        try:
            result = classify_message(message, context)
            return (
                result.is_correction and result.confidence >= 70,
                result.category,
                result.confidence / 100.0,  # Normalize to 0-1
                {
                    "reasoning": result.reasoning,
                    "what_was_wrong": result.what_was_wrong,
                    "scope": result.scope,
                    "semantic": True
                }
            )
        except Exception as e:
            # Fall back to regex if semantic fails
            pass
    
    # Fallback: Use regex result
    return is_candidate, regex_type, regex_confidence, {"semantic": False}


def extract_correction_context(message: str, my_previous_response: str = "") -> dict:
    """
    Extract what was wrong and what should be done from the message.
    """
    return {
        "user_message": message,
        "my_response": my_previous_response,
        "extracted_at": datetime.utcnow().isoformat(),
    }


def create_draft_correction(
    taught_by: str,
    authority: str,
    message: str,
    detection_type: str,
    confidence: float,
    context: dict,
    channel: str = "discord"
) -> dict:
    """
    Create a draft correction YAML structure.
    Draft corrections have status: pending and need human review.
    """
    correction_id = get_next_correction_id()
    
    draft = {
        "id": correction_id,
        "status": "pending",  # pending | approved | rejected
        "detected_at": datetime.utcnow().isoformat(),
        "detection": {
            "type": detection_type,
            "confidence": confidence,
            "source_channel": channel,
        },
        "taught_by": taught_by,
        "authority": authority,
        "raw_message": message,
        "context": context,
        # These need to be filled in by human or AI review
        "what_i_did": "",  # To be extracted
        "what_was_wrong": "",  # To be extracted
        "correct_behavior": "",  # To be extracted
        "severity": "medium",  # Default, to be adjusted
        "category": "behavior",  # Default, to be adjusted
        "triggers": [],  # To be extracted
        "scope": "universal",
    }
    
    return draft


def save_draft_correction(draft: dict) -> str:
    """Save a draft correction to the drafts directory."""
    drafts_dir = CORRECTIONS_DIR / "drafts"
    drafts_dir.mkdir(exist_ok=True)
    
    if HAS_YAML:
        filename = f"{draft['id']}-draft.yaml"
        filepath = drafts_dir / filename
        with open(filepath, 'w') as f:
            yaml.dump(draft, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
    else:
        # Fallback to JSON if yaml not available
        filename = f"{draft['id']}-draft.json"
        filepath = drafts_dir / filename
        with open(filepath, 'w') as f:
            json.dump(draft, f, indent=2)
    
    return str(filepath)


def process_message(
    message: str,
    sender_username: str,
    my_previous_response: str = "",
    channel: str = "discord",
    use_semantic: bool = True
) -> dict:
    """
    Process a message for potential corrections.
    
    Uses semantic classification by default for accurate detection.
    Distinguishes between corrections (log) and preferences/new_info (skip).
    
    Returns detection result with draft if found.
    """
    is_correction, category, confidence, details = detect_correction(
        message, 
        context=my_previous_response,
        use_semantic=use_semantic
    )
    
    result = {
        "is_correction": is_correction,
        "category": category,
        "confidence": confidence,
        "semantic_used": details.get("semantic", False),
        "reasoning": details.get("reasoning", ""),
        "draft_path": None,
    }
    
    # Auto-log if confidence >= 70%
    if is_correction and confidence >= 0.7:
        # Look up authority
        taught_by, authority = AUTHORITY_MAP.get(
            sender_username.lower(), 
            (sender_username, "member")
        )
        
        context = extract_correction_context(message, my_previous_response)
        
        # Enhance context with semantic details
        if details.get("what_was_wrong"):
            context["what_was_wrong"] = details["what_was_wrong"]
        if details.get("scope"):
            context["scope"] = details["scope"]
        
        draft = create_draft_correction(
            taught_by=taught_by,
            authority=authority,
            message=message,
            detection_type=category,
            confidence=confidence,
            context=context,
            channel=channel,
        )
        
        draft_path = save_draft_correction(draft)
        result["draft_path"] = draft_path
        result["draft"] = draft
        result["action"] = "logged"
    
    # Flag for review if 40-70%
    elif confidence >= 0.4:
        result["action"] = "flagged_for_review"
        result["reasoning"] = details.get("reasoning", "Confidence in uncertain range")
    
    # Skip if < 40%
    else:
        result["action"] = "skipped"
    
    return result


def main():
    """CLI interface for testing."""
    if len(sys.argv) < 3:
        print("Usage: capture.py <username> <message> [previous_response]")
        sys.exit(1)
    
    username = sys.argv[1]
    message = sys.argv[2]
    previous = sys.argv[3] if len(sys.argv) > 3 else ""
    
    result = process_message(message, username, previous)
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
