"""
Triage Spawner

Spawns Claude Code sessions to extract learnings from transcripts.
Uses the Claude Code CLI (free via Pro subscription).
"""

import sys
import asyncio
import subprocess
import tempfile
import threading
from pathlib import Path
from typing import Optional

from .cli_utils import get_subprocess_env, get_session_dir, CODECONTEXT_ROOT, get_logs_dir, build_claude_cli_args, encode_project_path
from .utils.timestamps import utc_now_iso


# Triage prompt template (V3)
TRIAGE_PROMPT = '''You are a memory extraction agent. Analyze this coding session transcript and extract key learnings.

IMPORTANT: Output ONLY valid XML. No explanations before or after.

---

## Output Format

<learnings>
  <learning>
    <type>bugfix|feature|discovery|decision|refactor|optimization|gotcha</type>
    <title>Short descriptive title (≤ 80 chars)</title>
    <fact>Specific fact with actual code, values, or commands used</fact>
    <files>comma-separated file paths, or "unknown" if not applicable</files>
  </learning>
</learnings>

---

## Extraction Stance: Quality Over Quantity

Extract learnings that meet ANY of these criteria:
1. A bug was identified and fixed (or the fix approach was discovered)
2. An architectural decision was made or system behavior was understood
3. A reusable pattern was implemented (CSS, API, config, etc.)
4. A non-obvious gotcha or pitfall was encountered

When truly uncertain (pure conversation, no code/discovery), SKIP.

---

## Hard Constraints

1. **Title MUST be ≤ 80 characters.** Truncate if needed.
2. **Files field**: Use ONLY file paths explicitly mentioned in transcript. If none are mentioned, use "unknown". Do NOT infer or guess paths. Separate paths with commas only. No words like "and".
3. **No duplicates**: Do not output duplicate or redundant learnings. Consolidate retries into a single learning.
4. **Quality filter (CRITICAL)**: You MUST skip trivial items. Ask: "Would a developer want to remember this in 6 months?" If uncertain, SKIP.
5. **Order by importance**: List learnings by impact (most impactful first). Prioritize architectural decisions, bug fixes, and permanent changes over temporary workarounds or minor refactors.
6. **Max 3 learnings**: Never output more than 3 <learning> entries.
7. **XML safety**: Escape special characters (<, >, &) in <title> and <fact> fields.
8. **File paths only**: Use repository-relative paths, not URLs or log references.

---

## Type Definitions & When to Use

### bugfix
**Definition**: An error, bug, or incorrect behavior was identified and corrected.
**When to use**: Something was broken and is now fixed.
**Examples**:
- Fixed null pointer exception in user lookup
- Corrected off-by-one error in pagination
- Resolved race condition in async handler

### feature
**Definition**: New functionality was added that did not exist before.
**When to use**: Users/systems can now do something they couldn't before.
**Examples**:
- Added export to CSV button
- Implemented new /api/users endpoint
- Created dark mode toggle component

### discovery
**Definition**: Learned something about existing code, system, or technology.
**When to use**: No code changed, but understanding increased.
**Examples**:
- Found that API uses cursor-based pagination, not offset
- Learned library requires explicit cleanup on unmount
- Discovered undocumented rate limit of 100 req/min

### decision
**Definition**: Made an architectural, design, or implementation choice.
**When to use**: Chose approach A over alternatives B, C with reasoning.
**Fact requirement**: Fact MUST include both the decision AND the reason.
**Examples**:
- Selected PostgreSQL over MongoDB for relational data needs
- Chose JWT with refresh tokens over session-based auth
- Decided to use server components for data-heavy pages

### refactor
**Definition**: Restructured code without changing external behavior.
**When to use**: Same inputs produce same outputs, but code is cleaner/better organized.
**Examples**:
- Extracted validation logic into reusable utility
- Renamed variables for clarity across module
- Consolidated duplicate API calls into single function

### optimization
**Definition**: Improved performance, efficiency, or resource usage.
**When to use**: Made something faster, smaller, or use fewer resources.
**Examples**:
- Added Redis caching, reduced DB queries by 80%
- Implemented lazy loading for images below fold
- Replaced O(n^2) algorithm with O(n log n) alternative

### gotcha
**Definition**: Identified a pitfall, anti-pattern, or thing to avoid.
**When to use**: Learned what NOT to do and why.
**Examples**:
- Don't use .env files with spaces in values - breaks parser
- Avoid nested useEffect - causes infinite render loop
- Never call setState in render - triggers re-render cascade

---

## Skip Response

If the transcript contains NO extractable learnings, respond with:

<learnings>
  <skip reason="REASON"/>
</learnings>

### Valid Skip Reasons (in precedence order)

If multiple skip reasons apply, use the most specific in this order:

| Priority | Reason | When to Use |
|----------|--------|-------------|
| 1 | conversational_only | Pure discussion with no technical content or code |
| 2 | administrative | File organization, git housekeeping, routine cleanup |
| 3 | incomplete | Transcript too short or corrupted to analyze |
| 4 | no_learnings | Technical work but nothing novel or worth remembering |

---

## What to Extract
- Errors and how they were fixed
- New files/functions created
- Architectural decisions and WHY
- Failed approaches (what NOT to do)
- Key commands that worked
- Dependencies or requirements discovered
- Performance improvements made

## What to Skip (STRICTLY ENFORCED)

### Obviously skip:
- Pure conversation about timelines or planning
- Routine file reads with no insight
- Simple navigation/listing commands
- Repeated similar operations
- Sessions with no code changes or discoveries

### Also skip (truly low-value items):
- Running install commands with no errors (npm install, pip install)
- Git operations (commit, push, pull) without insight
- Fixing typos or simple formatting
- Running tests that just pass (no debugging involved)

---

## Example Output

### Example 1: Normal extraction

<learnings>
  <learning>
    <type>bugfix</type>
    <title>Fixed JWT import error</title>
    <fact>Changed 'from jwt import encode' to 'import jwt' then 'jwt.encode()'. PyJWT uses module-level functions, not direct exports.</fact>
    <files>src/auth/jwt.py</files>
  </learning>
  <learning>
    <type>decision</type>
    <title>Used HS256 for JWT signing</title>
    <fact>Selected HS256 symmetric algorithm over RS256 because single-server setup doesn't need asymmetric keys. Secret stored in JWT_SECRET env var.</fact>
    <files>src/auth/jwt.py, .env.example</files>
  </learning>
  <learning>
    <type>gotcha</type>
    <title>PyJWT encode returns string in v2+</title>
    <fact>PyJWT 2.0+ returns string directly from encode(), not bytes. No need to call .decode('utf-8') anymore - doing so causes AttributeError.</fact>
    <files>src/auth/jwt.py</files>
  </learning>
</learnings>

### Example 2: Skip response

<learnings>
  <skip reason="conversational_only"/>
</learnings>

---

## Transcript to Analyze

{transcript}

---

Extract 1-3 learnings from the transcript above. If nothing worth extracting, use the skip response. Output ONLY the XML, nothing else.
'''


async def triage_and_store(
    transcript: str,
    task_name: str,
    project_path: str
) -> int:
    """
    Triage transcript and store learnings.

    Args:
        transcript: Formatted transcript from JSONL
        task_name: Name/description of the task
        project_path: Project path for memory storage

    Returns:
        Number of learnings stored
    """
    # Build prompt
    prompt = TRIAGE_PROMPT.format(transcript=transcript)

    # Spawn Claude Code for triage
    print("[CodeContext] Spawning triage session...")
    xml_output = await spawn_claude_code(prompt, project_path)

    if not xml_output:
        print("[CodeContext] Triage returned no output")
        return 0

    # Store in memory
    count = store_learnings(xml_output, task_name, project_path)
    print(f"[CodeContext] Stored {count} learnings")

    return count


def find_claude_cli() -> Optional[str]:
    """
    Find Claude Code CLI executable.

    Checks common installation locations on Windows.

    Returns:
        Path to claude executable, or None if not found
    """
    import shutil

    # Try PATH first
    claude_path = shutil.which("claude")
    if claude_path:
        return claude_path

    # Common Windows locations
    home = Path.home()
    candidates = [
        home / ".local" / "bin" / "claude.exe",
        home / ".local" / "bin" / "claude",
        home / "AppData" / "Local" / "Programs" / "claude" / "claude.exe",
        Path("C:/Program Files/claude/claude.exe"),
        Path("C:/Program Files (x86)/claude/claude.exe"),
    ]

    for candidate in candidates:
        if candidate.exists():
            return str(candidate)

    return None


async def spawn_claude_code(prompt: str, project_path: str) -> Optional[str]:
    """
    Spawn Claude Code CLI to process prompt.

    Uses --print flag for non-interactive output.
    Sets cwd to session_logs directory to isolate from user sessions.

    Args:
        prompt: The triage prompt
        project_path: Project path (used to set cwd for session isolation)

    Returns:
        Claude's response, or None on error
    """
    # Find Claude CLI
    claude_exe = find_claude_cli()
    if not claude_exe:
        print("[CodeContext] Claude Code CLI not found. Is it installed?")
        print("[CodeContext] Install with: npm install -g @anthropic/claude-code")
        return None

    # Write prompt to temp file (handles long prompts better)
    with tempfile.NamedTemporaryFile(
        mode='w',
        suffix='.txt',
        delete=False,
        encoding='utf-8'
    ) as f:
        f.write(prompt)
        prompt_file = f.name

    try:
        # Run Claude Code CLI
        # --print: Output only, no interactive mode
        # --dangerously-skip-permissions: Skip permission prompts (headless)
        # --model haiku: Use Haiku for faster, cheaper extraction
        # creationflags: Prevent console window on Windows
        # Get dedicated session directory for this script
        # Encode project path to ensure consistent naming across all scripts
        encoded_project = encode_project_path(project_path)
        session_logs_dir = get_session_dir(encoded_project, "memory_extractor")

        kwargs = {
            "stdout": asyncio.subprocess.PIPE,
            "stderr": asyncio.subprocess.PIPE,
            "env": get_subprocess_env(),
            "cwd": str(session_logs_dir),  # Isolate sessions from user's project
        }

        # Add Windows-specific flag to hide console window
        if sys.platform == 'win32':
            kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW

        cli_args = build_claude_cli_args(prompt, model="haiku")
        process = await asyncio.create_subprocess_exec(
            claude_exe,
            *cli_args,
            **kwargs
        )

        stdout, stderr = await asyncio.wait_for(
            process.communicate(),
            timeout=120  # 2 minute timeout
        )

        if process.returncode != 0:
            print(f"[CodeContext] Claude Code error: {stderr.decode()[:200]}")
            return None

        return stdout.decode()

    except asyncio.TimeoutError:
        print("[CodeContext] Triage timed out after 120s")
        return None

    except FileNotFoundError:
        print(f"[CodeContext] Claude Code CLI not found at: {claude_exe}")
        return None

    except Exception as e:
        print(f"[CodeContext] Triage error: {e}")
        return None

    finally:
        # Cleanup temp file
        try:
            Path(prompt_file).unlink()
        except Exception:
            pass


def spawn_triage_background(
    transcript: str,
    task_name: str,
    project_path: str
) -> None:
    """
    Spawn triage as a detached subprocess (truly non-blocking).

    The subprocess runs independently and survives parent exit.

    Args:
        transcript: Formatted transcript
        task_name: Task name/description
        project_path: Project path
    """
    import json as json_module

    # Write data to temp file for subprocess to read
    # NEW: Use CODECONTEXT_ROOT/temp instead of .autocoder/temp
    temp_dir = CODECONTEXT_ROOT / "temp"
    temp_dir.mkdir(parents=True, exist_ok=True)

    data_file = temp_dir / f"triage_data_{id(transcript)}.json"
    data = {
        "transcript": transcript,
        "task_name": task_name,
        "project_path": project_path
    }

    try:
        data_file.write_text(json_module.dumps(data), encoding='utf-8')
    except Exception as e:
        print(f"[CodeContext] Failed to write triage data: {e}")
        return

    # Spawn detached subprocess
    python_exe = sys.executable
    script_path = Path(__file__).resolve()

    try:
        # CREATE_NEW_PROCESS_GROUP and DETACHED_PROCESS for Windows
        # CREATE_NO_WINDOW prevents the console window from appearing
        # This allows the subprocess to survive parent exit
        if sys.platform == 'win32':
            CREATE_NEW_PROCESS_GROUP = 0x00000200
            DETACHED_PROCESS = 0x00000008
            CREATE_NO_WINDOW = 0x08000000
            subprocess.Popen(
                [python_exe, str(script_path), "--run-triage", str(data_file)],
                creationflags=CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_NO_WINDOW,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                stdin=subprocess.DEVNULL,
                env=get_subprocess_env(),  # Prevent hooks on subprocess
            )
        else:
            # Unix: use start_new_session
            subprocess.Popen(
                [python_exe, str(script_path), "--run-triage", str(data_file)],
                start_new_session=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                stdin=subprocess.DEVNULL,
                env=get_subprocess_env(),  # Prevent hooks on subprocess
            )
        print("[CodeContext] Triage spawned as background process")
    except Exception as e:
        print(f"[CodeContext] Failed to spawn triage: {e}")
        # Cleanup temp file on failure
        try:
            data_file.unlink()
        except:
            pass


def store_learnings(
    xml_output: str,
    task_name: str,
    project_path: str
) -> int:
    """
    Parse XML and store in CodeContext via HTTP API.

    Uses a resilient queue system: if the server is down, learnings are
    queued to disk and automatically submitted when the server comes back.

    Args:
        xml_output: XML from triage
        task_name: Task name
        project_path: Project path (used to derive project name)

    Returns:
        Number of learnings stored (or queued)
    """
    # Import memory adapter (same directory now)
    try:
        from context_adapter import ContextAdapter
    except ImportError as e:
        print(f"[CodeContext] Cannot import memory adapter v2: {e}")
        return 0

    # Determine project name from path - use encoded full path
    from cli_utils import encode_project_path
    project_name = encode_project_path(project_path) if project_path else "default"

    # Create adapter
    try:
        adapter = ContextAdapter(project=project_name)

        # Step 1: Try to flush any pending queue first
        pending_count = adapter.has_pending()
        if pending_count > 0:
            print(f"[CodeContext] Found {pending_count} pending batches in queue")
            flush_result = adapter.flush_queue()
            if flush_result.get("succeeded", 0) > 0:
                print(f"[CodeContext] Flushed {flush_result['succeeded']} queued batches")

        # Step 2: Parse new learnings from XML
        memories = adapter.parse_xml_to_memories(xml_output)
        if not memories:
            print("[CodeContext] No learnings to store from this session")
            return 0

        # Step 3: Check if server is running
        if not adapter.is_server_running():
            print("[CodeContext] Server not running - queueing learnings for later")
            print("[CodeContext] Start server with: codecontext server")
            if adapter.queue_memories(memories, project_name):
                return len(memories)  # Return queued count
            return 0

        # Step 4: Try to store new learnings directly
        count = adapter.store_from_xml(xml_output)

        # Step 5: If storage failed but we have memories, queue them
        if count == 0 and memories:
            print("[CodeContext] Direct storage failed - queueing for retry")
            adapter.queue_memories(memories, project_name)
            return len(memories)  # Return queued count

        return count
    except Exception as e:
        print(f"[CodeContext] Storage error: {e}")
        # Attempt to queue on any error
        try:
            if 'adapter' in locals() and 'memories' in locals() and memories:
                adapter.queue_memories(memories, project_name)
                print(f"[CodeContext] Queued {len(memories)} memories after error")
        except:
            pass
        return 0


def triage_sync(
    transcript: str,
    task_name: str,
    project_path: str
) -> int:
    """
    Synchronous wrapper for triage_and_store.

    Args:
        transcript: Formatted transcript
        task_name: Task name
        project_path: Project path

    Returns:
        Number of learnings stored
    """
    return asyncio.run(triage_and_store(transcript, task_name, project_path))


# CLI for testing and background execution
if __name__ == "__main__":
    import argparse
    import json as json_module

    parser = argparse.ArgumentParser(description="Triage Spawner")
    parser.add_argument("--transcript", type=str, help="Transcript text or file path")
    parser.add_argument("--task", type=str, default="Test Task", help="Task name")
    parser.add_argument("--project", type=str, default=".", help="Project path")
    parser.add_argument("--test", action="store_true", help="Run with test transcript")
    parser.add_argument("--run-triage", type=str, help="Run triage from data file (internal use)")

    args = parser.parse_args()

    # Handle background triage execution (called by spawn_triage_background)
    if args.run_triage:
        data_file = Path(args.run_triage)
        try:
            if data_file.exists():
                data = json_module.loads(data_file.read_text(encoding='utf-8'))
                # Run triage
                count = triage_sync(
                    data["transcript"],
                    data["task_name"],
                    data["project_path"]
                )
                # Log result to file for debugging
                # Use per-project logs directory
                log_file = get_logs_dir(data["project_path"]) / "extraction.log"
                with open(log_file, "a", encoding="utf-8") as f:
                    f.write(f"[{utc_now_iso()}] Triage complete: {count} learnings stored\n")
            else:
                print(f"[CodeContext] Data file not found: {data_file}")
        except Exception as e:
            # Log error - try per-project, fallback to global
            try:
                log_file = get_logs_dir(data["project_path"]) / "extraction.log"
            except:
                log_file = CODECONTEXT_ROOT / "extraction.log"
            with open(log_file, "a", encoding="utf-8") as f:
                f.write(f"[{utc_now_iso()}] Triage error: {e}\n")
        finally:
            # Cleanup data file
            try:
                if data_file.exists():
                    data_file.unlink()
            except:
                pass
        sys.exit(0)

    if args.test:
        # Test transcript
        transcript = """
# Task: Add authentication

**User**: Add JWT authentication to the API

**Claude**: I'll implement JWT authentication. Let me start by creating the auth module.

**Tool [Write]**:
File: src/auth/jwt.py
Content:
import jwt
from datetime import datetime, timedelta

SECRET_KEY = os.environ.get('JWT_SECRET', 'dev-secret')

def create_token(user_id: int) -> str:
    payload = {
        'user_id': user_id,
        'exp': datetime.utcnow() + timedelta(hours=24)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

✓ Result:
File written successfully

**Claude**: Now let me add the login endpoint.

**Tool [Write]**:
File: src/routes/auth.py
Content:
from flask import Blueprint, request, jsonify
from src.auth.jwt import create_token

auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/login', methods=['POST'])
def login():
    # ... implementation

✓ Result:
File written successfully

**Tool [Bash]**:
$ pytest tests/test_auth.py -v

✓ Result:
tests/test_auth.py::test_login PASSED
tests/test_auth.py::test_token_validation PASSED
"""
        print("Running with test transcript...")
        count = triage_sync(transcript, "Add authentication", args.project)
        print(f"\nStored {count} learnings")

    elif args.transcript:
        # Load from file if path
        if Path(args.transcript).exists():
            transcript = Path(args.transcript).read_text()
        else:
            transcript = args.transcript

        count = triage_sync(transcript, args.task, args.project)
        print(f"\nStored {count} learnings")

    else:
        print("Usage: codecontext extract --test")
        print("       codecontext extract --transcript 'transcript text' --task 'Task name'")
