"""
Cursor transcript parser.

Parses NDJSON transcript streams generated by Cursor CLI (--output stream-json).
"""

from __future__ import annotations

import json
import logging
from datetime import UTC, datetime
from typing import Any

from gobby.sessions.transcripts.base import ParsedMessage, TokenUsage

logger = logging.getLogger(__name__)

TOOL_OUTPUT_MAX_LENGTH = 500


class CursorTranscriptParser:
    """
    Parses NDJSON transcript files from Cursor CLI.

    Cursor uses --output stream-json to emit NDJSON events to stdout.
    Event types: system, user, assistant, tool_call, result.

    Key differences from Claude Code JSONL:
    - Tool calls use nested readToolCall/writeToolCall/function structures
    - type: "tool_call" with subtype: "started|completed"
    - session_id at top level of every event
    - No timestamp field
    """

    def __init__(self, logger_instance: logging.Logger | None = None):
        self.logger = logger_instance or logger

    def extract_last_messages(
        self, turns: list[dict[str, Any]], num_pairs: int = 2
    ) -> list[dict[str, Any]]:
        """Extract last N user<>agent message pairs."""
        messages: list[dict[str, Any]] = []
        for turn in reversed(turns):
            event_type = turn.get("type")
            if event_type == "user":
                content = self._extract_content_blocks(turn.get("message", {}).get("content", []))
                messages.insert(0, {"role": "user", "content": content})
            elif event_type == "assistant":
                content = self._extract_content_blocks(turn.get("message", {}).get("content", []))
                messages.insert(0, {"role": "assistant", "content": content})
            else:
                continue

            if len(messages) >= num_pairs * 2:
                break
        return messages

    def extract_turns_since_clear(
        self, turns: list[dict[str, Any]], max_turns: int = 50
    ) -> list[dict[str, Any]]:
        """Extract recent turns. Cursor doesn't have a /clear equivalent."""
        return turns[-max_turns:] if len(turns) > max_turns else turns

    def is_session_boundary(self, turn: dict[str, Any]) -> bool:
        """Cursor doesn't have session boundaries within a stream."""
        return False

    def parse_line(self, line: str, index: int) -> ParsedMessage | None:
        """
        Parse a single NDJSON line from Cursor's stream output.

        Event types:
        - system: init event with cwd, session_id, model, permissionMode
        - user: user message with content blocks
        - assistant: assistant response with content blocks
        - tool_call: tool invocation (subtype: started/completed)
        - result: final result summary
        """
        if not line.strip():
            return None

        try:
            data = json.loads(line)
        except json.JSONDecodeError:
            self.logger.warning("Invalid JSON at line %s", index)
            return None

        if not isinstance(data, dict):
            self.logger.debug("Skipping non-object JSON at line %s", index)
            return None

        event_type = data.get("type")
        timestamp = datetime.now(UTC)

        if event_type == "user":
            content = self._extract_content_blocks(data.get("message", {}).get("content", []))
            return ParsedMessage(
                index=index,
                role="user",
                content=content,
                content_type="text",
                tool_name=None,
                tool_input=None,
                tool_result=None,
                timestamp=timestamp,
                raw_json=data,
            )

        elif event_type == "assistant":
            content = self._extract_content_blocks(data.get("message", {}).get("content", []))
            return ParsedMessage(
                index=index,
                role="assistant",
                content=content,
                content_type="text",
                tool_name=None,
                tool_input=None,
                tool_result=None,
                timestamp=timestamp,
                raw_json=data,
            )

        elif event_type == "tool_call":
            subtype = data.get("subtype")
            tool_call_data = data.get("tool_call", {})

            if subtype == "started":
                tool_name, tool_input = self._extract_tool_call(tool_call_data)
                return ParsedMessage(
                    index=index,
                    role="assistant",
                    content=f"Tool call: {tool_name}",
                    content_type="tool_use",
                    tool_name=tool_name,
                    tool_input=tool_input,
                    tool_result=None,
                    timestamp=timestamp,
                    raw_json=data,
                    tool_use_id=data.get("call_id"),
                )

            elif subtype == "completed":
                tool_name, tool_result = self._extract_tool_result(tool_call_data)
                return ParsedMessage(
                    index=index,
                    role="tool",
                    content=str(tool_result.get("output", ""))[:TOOL_OUTPUT_MAX_LENGTH]
                    if tool_result
                    else "",
                    content_type="tool_result",
                    tool_name=tool_name,
                    tool_input=None,
                    tool_result=tool_result,
                    timestamp=timestamp,
                    raw_json=data,
                    tool_use_id=data.get("call_id"),
                )

        # Skip: system (init), result (summary)
        return None

    def _extract_content_blocks(self, content: Any) -> str:
        """Extract text from Cursor's content block format.

        Content is a list of blocks like [{"type": "text", "text": "..."}].
        """
        if isinstance(content, str):
            return content
        if not isinstance(content, list):
            return str(content or "")

        text_parts: list[str] = []
        for block in content:
            if isinstance(block, str):
                text_parts.append(block)
            elif isinstance(block, dict) and block.get("type") == "text":
                text_parts.append(block.get("text", ""))
        return "\n".join(text_parts)

    def _extract_tool_call(self, tool_call: dict[str, Any]) -> tuple[str, dict[str, Any] | None]:
        """Extract tool name and input from Cursor's nested tool_call structure.

        Cursor uses readToolCall, writeToolCall, or function as top-level keys.
        """
        for key in ("readToolCall", "writeToolCall", "function"):
            if key in tool_call:
                inner = tool_call[key]
                tool_name = key
                # For function calls, the name may be inside
                if key == "function" and "name" in inner:
                    tool_name = inner["name"]
                tool_input = inner.get("args") or inner.get("input") or inner.get("parameters")
                return tool_name, tool_input

        # Fallback: unknown structure
        return "unknown", tool_call

    def _extract_tool_result(self, tool_call: dict[str, Any]) -> tuple[str, dict[str, Any] | None]:
        """Extract tool name and result from a completed tool_call.

        The completed event contains the same nested structure but with a result field.
        """
        for key in ("readToolCall", "writeToolCall", "function"):
            if key in tool_call:
                inner = tool_call[key]
                tool_name = key
                if key == "function" and "name" in inner:
                    tool_name = inner["name"]
                result_data = inner.get("result", {})
                # Normalize result
                if isinstance(result_data, dict) and "success" in result_data:
                    output = result_data["success"].get("content", "")
                else:
                    output = str(result_data)
                return tool_name, {"output": output, "status": "success"}

        return "unknown", {"output": str(tool_call), "status": "unknown"}

    def _extract_usage(self, data: dict[str, Any]) -> TokenUsage | None:
        """Extract token usage from Cursor event data."""
        usage_data = data.get("usage")
        if not usage_data:
            return None

        return TokenUsage(
            input_tokens=usage_data.get("input_tokens", 0),
            output_tokens=usage_data.get("output_tokens", 0),
        )

    def parse_lines(self, lines: list[str], start_index: int = 0) -> list[ParsedMessage]:
        """Parse a list of NDJSON lines.

        Note: current_index tracks the parsed message count (incremented only
        for successfully parsed messages), not raw line numbers.
        """
        parsed_messages = []
        current_index = start_index

        for line in lines:
            message = self.parse_line(line, current_index)
            if message:
                parsed_messages.append(message)
                current_index += 1

        return parsed_messages
