"""Agent log introspection — parse running agent logs for detail-status."""
from __future__ import annotations

import json
import re
from dataclasses import dataclass, field
from typing import Any

# Tool-call patterns commonly seen in Claude Code / Codex agent output
_TOOL_NAMES = [
    "Read", "Edit", "Write", "Bash", "Grep", "Glob", "Agent",
    "TodoWrite", "NotebookEdit", "WebSearch", "WebFetch", "LSP",
]

_TOOL_RE = re.compile(
    r"\b(" + "|".join(_TOOL_NAMES) + r")\b", re.IGNORECASE
)

# File path patterns — match paths like /foo/bar.py, src/foo.ts, ./dir/file.md
_FILE_RE = re.compile(
    r"""(?:["'])?"""                     # optional opening quote
    r"((?:[A-Za-z]:[\\/]|/|\.{1,2}/)"   # root or relative prefix
    r"[A-Za-z0-9_./ \\-]+"              # path body
    r"\.[A-Za-z0-9]{1,10})"             # file extension
    r"""(?:["'])?"""                     # optional closing quote
)

# Simpler: catch common code files mentioned in logs
_SIMPLE_FILE_RE = re.compile(
    r"(?:^|[\s\"'`(])("
    r"(?:[\w./\\-]+/)?"      # optional directory prefix
    r"[\w.-]+\."             # filename.
    r"(?:py|ts|tsx|js|jsx|rs|go|java|c|cpp|h|hpp|md|json|yaml|yml|toml|sql|sh|css|html|svelte|vue)"
    r")(?:[\s\"'`),:;]|$)",
    re.MULTILINE,
)



@dataclass
class DetailStatus:
    """Structured summary of a running agent's current state."""
    task_id: str
    status: str  # "running", "completed", "failed", etc.
    current_activity: str = ""
    files_touched: list[str] = field(default_factory=list)
    tools_used: dict[str, int] = field(default_factory=dict)
    tokens: dict[str, Any] = field(default_factory=dict)
    lines_parsed: int = 0

    def to_dict(self) -> dict[str, Any]:
        return {
            "task_id": self.task_id,
            "status": self.status,
            "current_activity": self.current_activity,
            "files_touched": self.files_touched,
            "tools_used": self.tools_used,
            "tokens": self.tokens,
            "lines_parsed": self.lines_parsed,
        }


def parse_agent_log(log_content: str, task_id: str, task_status: str) -> DetailStatus:
    """Parse agent log content and extract structured detail-status.

    Reads the last 100 lines and extracts:
    - Current activity (last tool call or meaningful line)
    - Files touched (from tool calls and file references)
    - Tools used (count by type)
    - Tokens consumed (from JSON envelope if present)
    """
    ds = DetailStatus(task_id=task_id, status=task_status)

    if not log_content:
        return ds

    lines = log_content.splitlines()
    # Take last 100 lines for analysis
    tail = lines[-100:] if len(lines) > 100 else lines
    ds.lines_parsed = len(tail)

    all_files: list[str] = []
    tool_counts: dict[str, int] = {}
    last_activity = ""

    # Try to parse the whole content as a single JSON blob (completed task output)
    tokens = _extract_tokens_from_json(log_content)
    if tokens:
        ds.tokens = tokens

    for line in tail:
        stripped = line.strip()
        if not stripped:
            continue

        # Look for tool calls
        tool_matches = _TOOL_RE.findall(stripped)
        for tool in tool_matches:
            canonical = _canonical_tool_name(tool)
            tool_counts[canonical] = tool_counts.get(canonical, 0) + 1

        # Look for file references
        for m in _SIMPLE_FILE_RE.finditer(stripped):
            fpath = m.group(1)
            if fpath not in all_files:
                all_files.append(fpath)

        for m in _FILE_RE.finditer(stripped):
            fpath = m.group(1)
            if fpath not in all_files:
                all_files.append(fpath)

        # Track last meaningful activity
        activity = _extract_activity(stripped)
        if activity:
            last_activity = activity

    ds.current_activity = last_activity
    ds.files_touched = _filter_file_paths(all_files)
    ds.tools_used = tool_counts

    return ds


def _filter_file_paths(paths: list[str]) -> list[str]:
    """Filter out false-positive file path matches (CSS selectors, URLs, etc.)."""
    import re
    filtered = []
    for p in paths:
        # Skip CSS selectors (e.g., ".sidebar", ".queue-bar")
        if p.startswith(".") and "/" not in p and "\\" not in p:
            basename = p.lstrip(".")
            # Allow dotfiles like .env, .gitignore but not CSS class names
            if "-" in basename or basename.startswith(("seg", "task", "queue", "sidebar")):
                continue
        # Skip URLs / domains
        if (
            (".com" in p or ".org" in p or ".io" in p or ".dev" in p)
            and ("/" not in p or p.startswith("http"))
        ):
            continue
        # Skip lines starting with \n (escaped newlines in JSON)
        if p.startswith("\\n"):
            continue
        # Skip image/asset files that aren't code
        if p.endswith((
            ".gif", ".png", ".jpg", ".jpeg", ".ico", ".svg",
            ".woff", ".woff2", ".ttf", ".eot",
        )):
            continue
        # Skip version numbers (e.g., "1.0.0", "5.0.5", "0.1.1")
        if re.match(r"^\d+\.\d+\.\d+$", p):
            continue
        # Skip bare dotfolders (e.g., ".claude")
        if re.match(r"^\.[a-zA-Z]+$", p) and "/" not in p:
            continue
        # Skip arxiv/numeric IDs (e.g., "2510.15863")
        if re.match(r"^\d{4}\.\d+$", p):
            continue
        # Skip git diff fragments (e.g., "+37", "-12")
        if re.match(r"^[+-]\d+$", p):
            continue
        # Skip domain-like names without paths (e.g., "example.github.io")
        if re.match(r"^[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+$", p) and "/" not in p:
            ext = p.rsplit(".", 1)[-1] if "." in p else ""
            if ext not in ("py", "ts", "tsx", "js", "jsx", "rs", "go", "java", "c", "cpp",
                           "h", "hpp", "md", "json", "yaml", "yml", "toml", "sql", "sh",
                           "css", "html", "svelte", "vue"):
                continue
        filtered.append(p)
    # Deduplicate: prefer longest path for each basename
    seen_basenames: dict[str, str] = {}
    for p in filtered:
        base = p.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
        if base not in seen_basenames or len(p) > len(seen_basenames[base]):
            seen_basenames[base] = p
    return list(seen_basenames.values())


def _canonical_tool_name(name: str) -> str:
    """Normalize tool name to canonical casing."""
    lower = name.lower()
    mapping = {t.lower(): t for t in _TOOL_NAMES}
    return mapping.get(lower, name)


def _extract_activity(line: str) -> str:
    """Extract a human-readable activity description from a log line."""
    # Skip empty/noise lines
    if len(line) < 5:
        return ""
    if line.startswith("{") and line.endswith("}"):
        # JSON line — try to extract meaningful info
        try:
            obj = json.loads(line)
            if "result" in obj:
                result_text = obj["result"]
                if isinstance(result_text, str) and len(result_text) > 10:
                    # Return first sentence/line of result
                    first_line = result_text.split("\n")[0][:120]
                    return first_line
            if "type" in obj:
                return f"{obj['type']}: {obj.get('subtype', '')}"
        except (json.JSONDecodeError, TypeError):
            pass
        return ""

    # Tool call lines
    if _TOOL_RE.search(line):
        snippet = line[:150].strip()
        return snippet

    # Lines with file edits, commits, test results
    if any(kw in line.lower() for kw in ["commit", "pass", "fail", "error", "created", "modified"]):
        return line[:150].strip()

    # Generic meaningful line (not too short, not JSON noise)
    if len(line) > 20 and not line.startswith(("#", "//", "/*")):
        return line[:150].strip()

    return ""


def _extract_tokens_from_json(content: str) -> dict[str, Any]:
    """Try to extract token usage from Claude Code JSON output."""
    content = content.strip()
    # The log might be a single JSON line (Claude Code --output-format json)
    if not content.startswith("{"):
        return {}

    try:
        obj = json.loads(content)
    except (json.JSONDecodeError, TypeError):
        # Try last line
        last_line = content.strip().splitlines()[-1].strip()
        if last_line.startswith("{"):
            try:
                obj = json.loads(last_line)
            except (json.JSONDecodeError, TypeError):
                return {}
        else:
            return {}

    tokens: dict[str, Any] = {}

    # Direct usage field
    usage = obj.get("usage", {})
    if usage:
        for key in ("input_tokens", "output_tokens"):
            if key in usage:
                tokens[key] = usage[key]
        if "cache_read_input_tokens" in usage:
            tokens["cache_read_input_tokens"] = usage["cache_read_input_tokens"]
        if "cache_creation_input_tokens" in usage:
            tokens["cache_creation_input_tokens"] = usage["cache_creation_input_tokens"]

    # Cost
    if "total_cost_usd" in obj:
        tokens["cost_usd"] = obj["total_cost_usd"]

    # Model usage breakdown
    model_usage = obj.get("modelUsage", {})
    if model_usage:
        total_input = 0
        total_output = 0
        for _model, mu in model_usage.items():
            total_input += mu.get("inputTokens", 0) + mu.get("cacheReadInputTokens", 0)
            total_output += mu.get("outputTokens", 0)
        tokens["total_input_tokens"] = total_input
        tokens["total_output_tokens"] = total_output
        tokens["total_tokens"] = total_input + total_output

    # Num turns
    if "num_turns" in obj:
        tokens["num_turns"] = obj["num_turns"]

    # Duration
    if "duration_ms" in obj:
        tokens["duration_ms"] = obj["duration_ms"]

    return tokens
