#!/usr/bin/env python3
"""
telora-ai-hook - Claude Code hook for AI work tracking

Called by Claude Code PostToolUse hook to log tool calls.
Receives JSON input on stdin with tool_name, tool_input, tool_response.

Also detects Telora MCP issue status changes and updates the active
issue cache so telora-ai knows which issue to associate interactions with.
An issue becomes active when it moves to 'In Progress' and stops being
active when it moves anywhere else. With no active issue a tool call is
not logged, so the hook exits WITHOUT spawning `telora-ai` -- this hook
fires on every tool call, and a second interpreter start per call is the
whole of its cost.
"""

import json
import os
import sqlite3
import subprocess
import sys
from pathlib import Path

DB_PATH = Path.home() / '.syntelyos' / 'ai_tracking.db'

# The Telora MCP issue tool. Current form: one tool, an `action` argument.
MCP_ISSUE_TOOL = 'mcp__telora-products__telora_product_issue'
# Legacy per-action tool names, still recognised for older MCP servers.
MCP_ISSUE_UPDATE = 'mcp__telora-products__telora_product_issue_update'
MCP_ISSUE_CREATE = 'mcp__telora-products__telora_product_issue_create'

IN_PROGRESS = 'In Progress'


def active_issue_id():
    """The cached active issue's key, or None. Read directly (no subprocess)."""
    if not DB_PATH.exists():
        return None
    try:
        conn = sqlite3.connect(str(DB_PATH), timeout=1)
        try:
            row = conn.execute(
                "SELECT issue_key FROM active_issue_cache WHERE id = 1"
            ).fetchone()
        finally:
            conn.close()
    except sqlite3.Error:
        return None
    return row[0] if row and row[0] else None


def issue_action(tool_name, tool_input):
    """'create' / 'update' for a Telora issue write, else None."""
    if tool_name == MCP_ISSUE_TOOL and isinstance(tool_input, dict):
        action = tool_input.get('action')
        return action if action in ('create', 'update') else None
    if tool_name == MCP_ISSUE_UPDATE:
        return 'update'
    if tool_name == MCP_ISSUE_CREATE:
        return 'create'
    return None


def handle_telora_status_change(tool_name, tool_input, tool_response, cwd):
    """Detect MCP issue status changes and update the active issue cache."""
    action = issue_action(tool_name, tool_input)
    if not action or not isinstance(tool_input, dict):
        return
    status = tool_input.get('status')
    if not status:
        return  # Not a status change

    issue = parse_mcp_response(tool_response) or {}
    # The update response is compact ({id, status}); the id is always in the
    # request for an update, and in the response for a create.
    issue_id = tool_input.get('issueId') or issue.get('id')
    if not issue_id:
        return

    if status == IN_PROGRESS:
        activate_issue(
            issue_id,
            title=tool_input.get('title') or issue.get('title'),
            delivery_id=tool_input.get('deliveryId') or issue.get('deliveryId'),
            cwd=cwd,
        )
    elif action == 'update' and active_issue_id() == issue_id:
        # The active issue left In Progress (review, done, blocked, ...).
        deactivate_issue(cwd)


def parse_mcp_response(tool_response):
    """Parse the MCP tool response to extract issue details."""
    if not tool_response:
        return None

    data = tool_response
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except (json.JSONDecodeError, TypeError):
            return None

    if isinstance(data, list):
        data = {'content': data}

    if isinstance(data, dict):
        # Direct issue object
        if 'id' in data and 'content' not in data:
            return data
        # Wrapped in content/text from MCP
        if 'content' in data:
            content = data['content']
            if isinstance(content, list):
                for item in content:
                    if isinstance(item, dict) and item.get('type') == 'text':
                        try:
                            parsed = json.loads(item['text'])
                        except (json.JSONDecodeError, TypeError):
                            continue
                        if isinstance(parsed, dict):
                            return parsed
            elif isinstance(content, str):
                try:
                    parsed = json.loads(content)
                except (json.JSONDecodeError, TypeError):
                    return None
                if isinstance(parsed, dict):
                    return parsed

    return None


def activate_issue(issue_id, title=None, delivery_id=None, cwd=None):
    """Call telora-ai activate to cache the active issue (keyed by its UUID).

    Runs WITHOUT cwd: the cache is global, and a session directory that no
    longer exists would make the spawn fail -- silently, since hook errors
    are swallowed.
    """
    cmd = ['telora-ai', 'activate', issue_id, '--id', issue_id]
    if title:
        cmd.extend(['--title', title])
    if delivery_id:
        cmd.extend(['--delivery-id', delivery_id])

    try:
        subprocess.run(cmd, capture_output=True, timeout=5)
    except Exception:
        pass


def deactivate_issue(cwd):
    """Call telora-ai deactivate to clear the active issue cache."""
    try:
        subprocess.run(['telora-ai', 'deactivate'], capture_output=True, timeout=5)
    except Exception:
        pass


def main():
    # Read JSON input from stdin
    try:
        data = json.load(sys.stdin)
    except json.JSONDecodeError:
        sys.exit(0)  # Silently exit on invalid JSON

    tool_name = data.get('tool_name', '')
    tool_input = data.get('tool_input', {})
    tool_response = data.get('tool_response', {})
    cwd = data.get('cwd', os.getcwd())

    # Handle Telora MCP status changes (activate/deactivate issue cache)
    handle_telora_status_change(tool_name, tool_input, tool_response, cwd)

    # Nothing to attribute the call to: skip logging without a subprocess.
    if not active_issue_id():
        sys.exit(0)

    # Skip non-interesting tools for interaction logging
    skip_tools = {
        'Task', 'TodoWrite', 'AskUserQuestion', 'Skill',
        'EnterPlanMode', 'ExitPlanMode', 'KillShell', 'TaskOutput',
        'NotebookEdit', 'TaskCreate', 'TaskUpdate', 'TaskList', 'TaskGet',
    }
    if tool_name in skip_tools:
        sys.exit(0)

    if not isinstance(tool_input, dict):
        tool_input = {}

    # Determine target based on tool type
    target = None
    if tool_name == 'Read':
        target = tool_input.get('file_path', '')
    elif tool_name == 'Write':
        target = tool_input.get('file_path', '')
    elif tool_name == 'Edit':
        target = tool_input.get('file_path', '')
    elif tool_name == 'Bash':
        target = tool_input.get('command', '')[:200]  # Truncate long commands
    elif tool_name == 'Grep':
        target = tool_input.get('pattern', '')
    elif tool_name == 'Glob':
        target = tool_input.get('pattern', '')
    elif tool_name == 'WebFetch':
        target = tool_input.get('url', '')
    elif tool_name == 'WebSearch':
        target = tool_input.get('query', '')
    elif tool_name.startswith('mcp__'):
        target = tool_name

    # Determine success
    success = 'true'
    if isinstance(tool_response, dict):
        if tool_response.get('error') or tool_response.get('is_error'):
            success = 'false'

    # Get exit code for Bash
    exit_code = None
    if tool_name == 'Bash' and isinstance(tool_response, dict):
        exit_code = tool_response.get('exit_code')

    # Build command
    cmd = ['telora-ai', 'log', 'tool_call', '--tool', tool_name, '--success', success]
    if target:
        cmd.extend(['--target', target])
    if exit_code is not None:
        cmd.extend(['--exit-code', str(exit_code)])
    cmd.extend(['--repo', cwd])

    # Run telora-ai log command
    try:
        subprocess.run(cmd, capture_output=True, timeout=5)
    except Exception:
        pass  # Silently fail to not disrupt Claude Code


if __name__ == '__main__':
    main()
