"""Extract structured analytics from Claude Code session transcripts."""

from __future__ import annotations

import json
from collections import Counter
from pathlib import Path
from typing import Any

from blackbox.dashboard.transcript import ts_to_epoch
from blackbox.models import (
    CODEFLASH_AGENT_PREFIXES,
    CODEFLASH_COMMANDS,
    CODEFLASH_SKILLS,
    CodeflashSession,
    SessionMeta,
)

EDIT_TOOLS = {"Edit", "Write", "NotebookEdit"}
FILE_EXTENSIONS: dict[str, str] = {
    ".py": "python",
    ".js": "javascript",
    ".ts": "typescript",
    ".tsx": "typescript",
    ".jsx": "javascript",
    ".go": "go",
    ".rs": "rust",
    ".java": "java",
    ".rb": "ruby",
    ".sh": "shell",
    ".bash": "shell",
    ".zsh": "shell",
    ".toml": "toml",
    ".yaml": "yaml",
    ".yml": "yaml",
    ".json": "json",
    ".md": "markdown",
    ".html": "html",
    ".css": "css",
}


def extract_meta(path: Path) -> SessionMeta | None:  # noqa: C901, PLR0912, PLR0915
    """Extract a SessionMeta from a raw .jsonl transcript."""
    session_id = path.stem
    project_path = path.parent.name

    timestamps: list[float] = []
    user_messages = 0
    assistant_messages = 0
    tool_calls = 0
    tool_counts: Counter[str] = Counter()
    tool_errors = 0
    tool_error_categories: Counter[str] = Counter()
    tool_error_details: list[tuple[str, str]] = []
    input_tokens = 0
    output_tokens = 0
    cache_read_tokens = 0
    cache_creation_tokens = 0
    files_modified: set[str] = set()
    lines_added = 0
    lines_removed = 0
    git_commits = 0
    git_branch: str | None = None
    user_interruptions = 0
    compactions = 0
    subagents_spawned = 0
    thinking_blocks = 0
    web_searches = 0
    web_fetches = 0
    permission_mode: str | None = None
    languages: Counter[str] = Counter()
    first_prompt = ""
    pending_tools: dict[str, str] = {}
    codeflash_agents: set[str] = set()
    codeflash_skills: set[str] = set()
    codeflash_commands: set[str] = set()
    teams_created = 0

    try:
        text = path.read_bytes().decode("utf-8")
    except OSError:
        return None

    _loads = json.loads
    for line in text.splitlines():
        if not line or line.isspace():
            continue
        try:
            raw = _loads(line)
        except json.JSONDecodeError:
            continue

        ts = ts_to_epoch(raw.get("timestamp"))
        if ts:
            timestamps.append(ts)

        entry_type = raw.get("type", "")

        if entry_type == "permission-mode":
            permission_mode = raw.get("permissionMode")
            continue

        if entry_type == "summary":
            compactions += 1
            continue

        if git_branch is None and raw.get("gitBranch"):
            git_branch = raw["gitBranch"]

        if entry_type == "user":
            msg = raw.get("message", {})
            if not isinstance(msg, dict):
                continue
            content = msg.get("content", "")
            if isinstance(content, str) and content.strip():
                user_messages += 1
                if not first_prompt:
                    first_prompt = content[:120]
            elif isinstance(content, list):
                has_tool_result = any(isinstance(b, dict) and b.get("type") == "tool_result" for b in content)
                if has_tool_result:
                    for block in content:
                        if not isinstance(block, dict) or block.get("type") != "tool_result":
                            continue
                        tool_use_id = block.get("tool_use_id", "")
                        is_error = block.get("is_error", False)
                        if is_error:
                            tool_errors += 1
                            tool_name = pending_tools.get(tool_use_id, "unknown")
                            category = classify_error(tool_name, block, raw)
                            tool_error_categories[category] += 1
                            stderr = ""
                            tur = raw.get("toolUseResult", {})
                            if isinstance(tur, dict):
                                stderr = tur.get("stderr", "")
                            detail_text = stderr or str(block.get("content", ""))[:200]
                            tool_error_details.append((category, detail_text))
                else:
                    user_messages += 1
                    if not first_prompt:
                        texts = [b.get("text", "") for b in content if isinstance(b, dict) and b.get("type") == "text"]
                        first_prompt = " ".join(texts)[:120]

            interrupted = False
            tur = raw.get("toolUseResult", {})
            if isinstance(tur, dict):
                interrupted = tur.get("interrupted", False)
            if interrupted:
                user_interruptions += 1

        elif entry_type == "assistant":
            msg = raw.get("message", {})
            if not isinstance(msg, dict):
                continue
            usage = msg.get("usage", {})
            if usage:
                input_tokens += usage.get("input_tokens", 0)
                output_tokens += usage.get("output_tokens", 0)
                cache_read_tokens += usage.get("cache_read_input_tokens", 0)
                cache_creation_tokens += usage.get("cache_creation_input_tokens", 0)
                stu = usage.get("server_tool_use", {})
                if stu:
                    web_searches += stu.get("web_search_requests", 0)
                    web_fetches += stu.get("web_fetch_requests", 0)

            content = msg.get("content", [])
            if not isinstance(content, list):
                assistant_messages += 1
                continue

            has_text = False
            for block in content:
                if not isinstance(block, dict):
                    continue
                btype = block.get("type", "")
                if btype == "text" and block.get("text", "").strip():
                    has_text = True
                elif btype == "thinking":
                    thinking_blocks += 1
                elif btype == "tool_use":
                    tool_name = block.get("name", "unknown")
                    tool_calls += 1
                    tool_counts[tool_name] += 1
                    tool_id = block.get("id", "")
                    if tool_id:
                        pending_tools[tool_id] = tool_name
                    if tool_name == "Agent":
                        subagents_spawned += 1
                    if tool_name == "TeamCreate":
                        teams_created += 1
                    tool_input = block.get("input", {})
                    if isinstance(tool_input, dict):
                        track_file_changes(
                            tool_name,
                            tool_input,
                            files_modified,
                            languages,
                        )
                        lines_a, lines_r = count_diff_lines(tool_name, tool_input)
                        lines_added += lines_a
                        lines_removed += lines_r
                        if tool_name == "Bash":
                            cmd = tool_input.get("command", "")
                            if isinstance(cmd, str) and "git commit" in cmd and "--amend" not in cmd:
                                git_commits += 1
                        if tool_name == "Agent":
                            agent_name = tool_input.get("name", "") or tool_input.get("subagent_type", "")
                            if agent_name in CODEFLASH_AGENT_PREFIXES:
                                codeflash_agents.add(agent_name)
                        if tool_name == "Skill":
                            skill_name = tool_input.get("skill", "")
                            if skill_name in CODEFLASH_SKILLS:
                                codeflash_skills.add(skill_name)
                            if skill_name in CODEFLASH_COMMANDS:
                                codeflash_commands.add(skill_name)
            if has_text:
                assistant_messages += 1

    if not timestamps:
        return None

    start_time = min(timestamps)
    end_time = max(timestamps)

    return SessionMeta(
        session_id=session_id,
        project_path=project_path,
        transcript_path=str(path),
        start_time=start_time,
        end_time=end_time,
        duration_s=end_time - start_time,
        user_messages=user_messages,
        assistant_messages=assistant_messages,
        tool_calls=tool_calls,
        tool_counts=dict(tool_counts),
        tool_errors=tool_errors,
        tool_error_categories=dict(tool_error_categories),
        tool_error_details=tuple(tool_error_details),
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        cache_read_tokens=cache_read_tokens,
        cache_creation_tokens=cache_creation_tokens,
        languages=dict(languages),
        files_modified=len(files_modified),
        lines_added=lines_added,
        lines_removed=lines_removed,
        git_commits=git_commits,
        git_branch=git_branch,
        user_interruptions=user_interruptions,
        compactions=compactions,
        subagents_spawned=subagents_spawned,
        thinking_blocks=thinking_blocks,
        web_searches=web_searches,
        web_fetches=web_fetches,
        permission_mode=permission_mode,
        first_prompt=first_prompt,
        codeflash=detect_codeflash(codeflash_agents, codeflash_skills, codeflash_commands, teams_created),
    )


def classify_error(tool_name: str, block: dict[str, Any], raw: dict[str, Any]) -> str:
    """Classify a tool error into a category based on tool name and error content."""
    tur = raw.get("toolUseResult", {})
    stderr = ""
    if isinstance(tur, dict):
        stderr = tur.get("stderr", "")
    error_text = (stderr or str(block.get("content", ""))).lower()

    if tool_name == "Edit":
        return "edit_failed"
    if tool_name == "Bash":
        return classify_bash_error(error_text)
    if tool_name in ("Read", "Write"):
        return classify_file_error(error_text)
    return "tool_error"


def classify_bash_error(error_text: str) -> str:
    if "permission denied" in error_text:
        return "permission_denied"
    if "command not found" in error_text:
        return "command_not_found"
    return "command_failed"


def classify_file_error(error_text: str) -> str:
    if "not found" in error_text or "no such file" in error_text:
        return "file_not_found"
    return "file_error"


def track_file_changes(
    tool_name: str,
    tool_input: dict[str, Any],
    files: set[str],
    languages: Counter[str],
) -> None:
    """Track which files were modified and what languages are involved."""
    if tool_name not in EDIT_TOOLS:
        return
    fp = tool_input.get("file_path", "")
    if not fp:
        return
    files.add(fp)
    dot = fp.rfind(".")
    lang = None if dot == -1 else FILE_EXTENSIONS.get(fp[dot:].lower())
    if lang:
        languages[lang] += 1


def count_diff_lines(tool_name: str, tool_input: dict[str, Any]) -> tuple[int, int]:
    """Estimate lines added/removed from Edit and Write tool inputs."""
    if tool_name == "Edit":
        old = tool_input.get("old_string", "")
        new = tool_input.get("new_string", "")
        if isinstance(old, str) and isinstance(new, str):
            old_lines = old.count("\n") + (1 if old else 0)
            new_lines = new.count("\n") + (1 if new else 0)
            added = max(0, new_lines - old_lines)
            removed = max(0, old_lines - new_lines)
            return added, removed
    if tool_name == "Write":
        content = tool_input.get("content", "")
        if isinstance(content, str):
            return content.count("\n") + 1, 0
    return 0, 0


def detect_codeflash(
    agents: set[str],
    skills: set[str],
    commands: set[str],
    teams_created: int,
) -> CodeflashSession | None:
    """Build a CodeflashSession if any codeflash plugin signals were detected."""
    if not agents and not skills and not commands:
        return None

    language = infer_language(agents)
    domain = infer_domain(agents)

    return CodeflashSession(
        is_codeflash=True,
        language=language,
        agents_used=tuple(sorted(agents)),
        skills_invoked=tuple(sorted(skills)),
        commands_invoked=tuple(sorted(commands)),
        teams_created=teams_created,
        optimization_domain=domain,
        has_researcher="codeflash-researcher" in agents,
        has_reviewer="codeflash-review" in agents,
        has_ci_handler=any(a.endswith("-ci") for a in agents),
        has_pr_prep=any(a.endswith("-pr-prep") for a in agents),
    )


LANGUAGE_AGENT_MARKERS: dict[str, str] = {
    "codeflash-python": "python",
    "codeflash-javascript": "javascript",
    "codeflash-java": "java",
}


def infer_language(agents: set[str]) -> str | None:
    """Infer the target language from which language-specific agents were invoked."""
    for marker, lang in LANGUAGE_AGENT_MARKERS.items():
        if marker in agents:
            return lang
    for agent in agents:
        if agent.startswith("codeflash-js-"):
            return "javascript"
        if agent.startswith("codeflash-java-"):
            return "java"
    return None


DOMAIN_AGENT_SUFFIXES: dict[str, str] = {
    "-cpu": "cpu",
    "-memory": "memory",
    "-async": "async",
    "-structure": "structure",
    "-deep": "deep",
    "-bundle": "bundle",
}


def infer_domain(agents: set[str]) -> str | None:
    """Infer the optimization domain from specialist agents used."""
    for agent in agents:
        for suffix, domain in DOMAIN_AGENT_SUFFIXES.items():
            if agent.endswith(suffix):
                return domain
    return None
