"""Interactive replay debugger for stepping through transcripts.

This module provides a simple interactive debugger that allows
stepping through transcript records one by one, setting breakpoints,
and inspecting record details.
"""

from __future__ import annotations

import cmd
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Set


@dataclass
class Breakpoint:
    """A breakpoint condition."""

    id: int
    record_type: Optional[str] = None
    field_match: Optional[Dict[str, Any]] = None

    def matches(self, record: Dict[str, Any]) -> bool:
        """Check if record matches this breakpoint."""
        if self.record_type and record.get("type") != self.record_type:
            return False

        if self.field_match:
            for key, value in self.field_match.items():
                if key not in record:
                    return False
                if isinstance(value, str) and value in str(record[key]):
                    continue
                if record[key] != value:
                    return False

        return True

    def __str__(self) -> str:
        parts = []
        if self.record_type:
            parts.append(f"type={self.record_type}")
        if self.field_match:
            for k, v in self.field_match.items():
                parts.append(f"{k}={v}")
        return f"Breakpoint {self.id}: {' '.join(parts) or 'all'}"


@dataclass
class DebuggerState:
    """State of the replay debugger."""

    transcript: List[Dict[str, Any]]
    position: int = 0
    breakpoints: List[Breakpoint] = field(default_factory=list)
    next_bp_id: int = 1
    watches: Set[str] = field(default_factory=set)

    @property
    def current_record(self) -> Optional[Dict[str, Any]]:
        if 0 <= self.position < len(self.transcript):
            return self.transcript[self.position]
        return None

    @property
    def at_end(self) -> bool:
        return self.position >= len(self.transcript)


class ReplayDebugger(cmd.Cmd):
    """Interactive replay debugger."""

    intro = """
AutoDebug Replay Debugger
=========================
Commands: next, step, continue, break, list, inspect, search, quit
Type 'help' for more information.
"""
    prompt = "(adb) "

    def __init__(self, transcript: List[Dict[str, Any]]):
        super().__init__()
        self.state = DebuggerState(transcript=transcript)

    def _show_record(self, record: Dict[str, Any], verbose: bool = False) -> None:
        """Display a record."""
        seq = record.get("seq", "?")
        rtype = record.get("type", "UNKNOWN")

        # Format based on type
        if rtype == "HTTP":
            method = record.get("method", "?")
            url = record.get("url", record.get("url_canonical", "?"))
            status = record.get("status", "?")
            print(f"[seq {seq}] HTTP {method} {url}")
            print(f"  Status: {status}")
            if verbose:
                if "request_body" in record:
                    print(f"  Request Body: {self._truncate(record['request_body'])}")
                if "response_body" in record:
                    print(f"  Response Body: {self._truncate(record['response_body'])}")

        elif rtype == "SQL":
            query = record.get("query", "?")
            rows = record.get("rowcount", record.get("rows", "?"))
            print(f"[seq {seq}] SQL")
            print(f"  Query: {self._truncate(query, 100)}")
            print(f"  Rows: {rows}")

        elif rtype == "REDIS":
            command = record.get("command", "?")
            key = record.get("key", "")
            print(f"[seq {seq}] REDIS {command} {key}")
            if verbose and "result" in record:
                print(f"  Result: {self._truncate(record['result'])}")

        elif rtype == "MONGODB":
            op = record.get("operation", "?")
            collection = record.get("collection", "?")
            print(f"[seq {seq}] MONGODB {op} on {collection}")
            if verbose and "result_count" in record:
                print(f"  Results: {record['result_count']}")

        elif rtype == "AWS":
            service = record.get("service", "?")
            operation = record.get("operation", "?")
            print(f"[seq {seq}] AWS {service}.{operation}")
            if "bucket" in record:
                print(f"  Bucket: {record['bucket']}, Key: {record.get('key', '?')}")

        elif rtype == "GRPC":
            method = record.get("method", "?")
            print(f"[seq {seq}] GRPC {method}")

        elif rtype in ("TIME", "RNG", "ASYNC_SLEEP"):
            print(f"[seq {seq}] {rtype}")
            if "value" in record:
                print(f"  Value: {record['value']}")
            if "delay" in record:
                print(f"  Delay: {record['delay']}")

        elif rtype == "EXCEPTION":
            exc_type = record.get("exc_type", "?")
            message = record.get("message", "")
            print(f"[seq {seq}] EXCEPTION {exc_type}")
            print(f"  Message: {self._truncate(message)}")

        else:
            print(f"[seq {seq}] {rtype}")
            if verbose:
                for key, value in record.items():
                    if key not in ("seq", "type", "ts"):
                        print(f"  {key}: {self._truncate(value)}")

    def _truncate(self, value: Any, max_len: int = 60) -> str:
        s = str(value)
        if len(s) > max_len:
            return s[: max_len - 3] + "..."
        return s

    def _check_breakpoints(self, record: Dict[str, Any]) -> Optional[Breakpoint]:
        """Check if any breakpoint matches the record."""
        for bp in self.state.breakpoints:
            if bp.matches(record):
                return bp
        return None

    # Commands

    def do_next(self, arg: str) -> None:
        """Move to the next record. Usage: next [n]"""
        count = int(arg) if arg else 1

        for _ in range(count):
            if self.state.at_end:
                print("End of transcript")
                return

            self.state.position += 1

            if self.state.current_record:
                self._show_record(self.state.current_record)

    do_n = do_next

    def do_step(self, arg: str) -> None:
        """Step to next record with verbose output. Usage: step"""
        if self.state.at_end:
            print("End of transcript")
            return

        self.state.position += 1

        if self.state.current_record:
            self._show_record(self.state.current_record, verbose=True)

    do_s = do_step

    def do_continue(self, arg: str) -> None:
        """Continue until next breakpoint or end. Usage: continue"""
        while not self.state.at_end:
            self.state.position += 1

            record = self.state.current_record
            if record is None:
                break

            bp = self._check_breakpoints(record)
            if bp:
                print(f"{bp}")
                self._show_record(record)
                return

        print("End of transcript")

    do_c = do_continue

    def do_break(self, arg: str) -> None:
        """Set a breakpoint. Usage: break [TYPE] [field=value]

        Examples:
            break HTTP
            break SQL
            break HTTP status=404
            break EXCEPTION
        """
        bp = Breakpoint(id=self.state.next_bp_id)
        self.state.next_bp_id += 1

        parts = arg.split()
        for part in parts:
            if "=" in part:
                key, value = part.split("=", 1)
                if bp.field_match is None:
                    bp.field_match = {}
                # Try to parse as int
                try:
                    bp.field_match[key] = int(value)
                except ValueError:
                    bp.field_match[key] = value
            else:
                bp.record_type = part.upper()

        self.state.breakpoints.append(bp)
        print(f"Added {bp}")

    do_b = do_break

    def do_delete(self, arg: str) -> None:
        """Delete a breakpoint. Usage: delete <id>"""
        try:
            bp_id = int(arg)
            self.state.breakpoints = [
                bp for bp in self.state.breakpoints if bp.id != bp_id
            ]
            print(f"Deleted breakpoint {bp_id}")
        except ValueError:
            print("Usage: delete <breakpoint_id>")

    do_d = do_delete

    def do_list(self, arg: str) -> None:
        """List records around current position. Usage: list [n]"""
        count = int(arg) if arg else 5

        start = max(0, self.state.position - count // 2)
        end = min(len(self.state.transcript), start + count)

        for i in range(start, end):
            marker = ">>>" if i == self.state.position else "   "
            record = self.state.transcript[i]
            seq = record.get("seq", i)
            rtype = record.get("type", "UNKNOWN")

            summary = self._get_record_summary(record)
            print(f"{marker} [{seq}] {rtype}: {summary}")

    do_l = do_list

    def _get_record_summary(self, record: Dict[str, Any]) -> str:
        """Get a one-line summary of a record."""
        rtype = record.get("type", "UNKNOWN")

        if rtype == "HTTP":
            return f"{record.get('method', '?')} {record.get('url', '?')[:40]}"
        elif rtype == "SQL":
            return self._truncate(record.get("query", "?"), 50)
        elif rtype == "REDIS":
            return f"{record.get('command', '?')} {record.get('key', '')}"
        elif rtype == "MONGODB":
            return f"{record.get('operation', '?')} on {record.get('collection', '?')}"
        elif rtype == "AWS":
            return f"{record.get('service', '?')}.{record.get('operation', '?')}"
        elif rtype == "EXCEPTION":
            return record.get("exc_type", "?")
        else:
            return ""

    def do_inspect(self, arg: str) -> None:
        """Inspect current or specified record. Usage: inspect [seq]"""
        if arg:
            try:
                seq = int(arg)
                for record in self.state.transcript:
                    if record.get("seq") == seq:
                        print(json.dumps(record, indent=2, default=str))
                        return
                print(f"No record with seq {seq}")
            except ValueError:
                print("Usage: inspect [seq]")
        else:
            if self.state.current_record:
                print(json.dumps(self.state.current_record, indent=2, default=str))
            else:
                print("No current record")

    do_i = do_inspect

    def do_search(self, arg: str) -> None:
        """Search for records matching pattern. Usage: search <pattern>"""
        if not arg:
            print("Usage: search <pattern>")
            return

        pattern = arg.lower()
        matches = []

        for i, record in enumerate(self.state.transcript):
            record_str = json.dumps(record, default=str).lower()
            if pattern in record_str:
                matches.append((i, record))

        if not matches:
            print(f"No records matching '{arg}'")
            return

        print(f"Found {len(matches)} matches:")
        for i, record in matches[:10]:
            seq = record.get("seq", i)
            rtype = record.get("type", "UNKNOWN")
            summary = self._get_record_summary(record)
            print(f"  [{seq}] {rtype}: {summary}")

        if len(matches) > 10:
            print(f"  ... and {len(matches) - 10} more")

    def do_goto(self, arg: str) -> None:
        """Go to a specific sequence number. Usage: goto <seq>"""
        try:
            seq = int(arg)
            for i, record in enumerate(self.state.transcript):
                if record.get("seq") == seq:
                    self.state.position = i
                    self._show_record(record)
                    return
            print(f"No record with seq {seq}")
        except ValueError:
            print("Usage: goto <seq>")

    do_g = do_goto

    def do_summary(self, arg: str) -> None:
        """Show transcript summary."""
        by_type: Dict[str, int] = {}
        for record in self.state.transcript:
            rtype = record.get("type", "UNKNOWN")
            by_type[rtype] = by_type.get(rtype, 0) + 1

        print("\nTranscript Summary")
        print("==================")
        print(f"Total records: {len(self.state.transcript)}")
        print(f"Current position: {self.state.position}")
        print("\nBy type:")
        for rtype, count in sorted(by_type.items(), key=lambda x: -x[1]):
            print(f"  {rtype}: {count}")

        print(f"\nBreakpoints: {len(self.state.breakpoints)}")
        for bp in self.state.breakpoints:
            print(f"  {bp}")

    def do_reset(self, arg: str) -> None:
        """Reset to beginning of transcript."""
        self.state.position = 0
        print("Reset to beginning")

    def do_quit(self, arg: str) -> bool:
        """Exit the debugger."""
        print("Goodbye")
        return True

    do_q = do_quit
    do_exit = do_quit

    def emptyline(self) -> None:
        """On empty line, do nothing (don't repeat last command)."""
        pass


def load_and_debug(transcript_path: Path) -> None:
    """Load a transcript and start the debugger.

    Args:
        transcript_path: Path to JSONL transcript file
    """
    records = []
    with transcript_path.open("r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                records.append(json.loads(line))

    records.sort(key=lambda r: r.get("seq", 0))

    print(f"Loaded {len(records)} records from {transcript_path}")

    debugger = ReplayDebugger(records)
    debugger.cmdloop()
