"""Command result logging for support diagnostics."""

from __future__ import annotations

import hashlib
import json
import os
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

MAX_ENTRIES = 200
MAX_BYTES = 2 * 1024 * 1024


@dataclass
class CommandLogEntry:
    timestamp: str
    command: str
    args_redacted: list[str]
    exit_code: int
    correlation_id: str
    status: str
    duration_ms: int
    error_fingerprint: str | None
    agentic_result: dict[str, Any] | None
    version: str

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def _utc_timestamp() -> str:
    return datetime.now(timezone.utc).isoformat()


def _support_data_dir() -> Path:
    try:
        from platformdirs import user_data_dir

        base = Path(user_data_dir("g_gremlin"))
    except Exception:
        base = Path.home() / ".g_gremlin"
    return base / "support"


def support_log_path() -> Path:
    return _support_data_dir() / "command_log.jsonl"


def _lock_handle(handle):
    try:
        import portalocker

        portalocker.lock(handle, portalocker.LOCK_EX)

        def _unlock() -> None:
            portalocker.unlock(handle)

        return _unlock
    except Exception:
        if os.name == "nt":
            import msvcrt

            handle.seek(0)
            msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)

            def _unlock() -> None:
                handle.seek(0)
                msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)

            return _unlock

        else:
            import fcntl

            fcntl.flock(handle.fileno(), fcntl.LOCK_EX)

            def _unlock() -> None:
                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)

            return _unlock


def append_entry(entry: CommandLogEntry, path: Path | None = None) -> Path:
    log_path = path or support_log_path()
    log_path.parent.mkdir(parents=True, exist_ok=True)
    line = json.dumps(entry.to_dict(), ensure_ascii=True)

    with log_path.open("a+", encoding="utf-8") as handle:
        unlock = _lock_handle(handle)
        try:
            handle.write(line + "\n")
            handle.flush()
            _truncate_if_needed(log_path, handle=handle)
        finally:
            unlock()
    return log_path


def read_recent(n: int = 50, path: Path | None = None) -> list[dict[str, Any]]:
    if n <= 0:
        return []
    log_path = path or support_log_path()
    if not log_path.exists():
        return []
    try:
        content = log_path.read_text(encoding="utf-8", errors="ignore")
    except Exception:
        return []
    lines = [line for line in content.splitlines() if line.strip()]
    if not lines:
        return []
    tail = lines[-n:]
    entries: list[dict[str, Any]] = []
    for line in tail:
        try:
            entries.append(json.loads(line))
        except Exception:
            continue
    return entries


def _truncate_if_needed(path: Path, handle=None) -> None:
    if not path.exists():
        return
    try:
        if handle is not None:
            handle.flush()
            handle.seek(0, os.SEEK_END)
            size = handle.tell()
        else:
            size = path.stat().st_size
    except Exception:
        return

    if handle is not None:
        try:
            handle.seek(0)
            content = handle.read()
        except Exception:
            return
    else:
        content = path.read_text(encoding="utf-8", errors="ignore")

    lines = [line + "\n" for line in content.splitlines() if line.strip()]
    if size <= MAX_BYTES and len(lines) <= MAX_ENTRIES:
        return

    if not lines:
        return

    trimmed = lines[-MAX_ENTRIES:]
    joined = "".join(trimmed)
    while len(joined.encode("utf-8")) > MAX_BYTES and len(trimmed) > 1:
        trimmed = trimmed[1:]
        joined = "".join(trimmed)

    if handle is not None:
        try:
            handle.seek(0)
            handle.truncate(0)
            handle.write(joined)
            handle.flush()
        except Exception:
            return
    else:
        path.write_text(joined, encoding="utf-8")


def _hash_text(value: str) -> str:
    digest = hashlib.sha256()
    digest.update(value.encode("utf-8"))
    return digest.hexdigest()


def compute_error_fingerprint(
    agentic_result: dict[str, Any] | None,
    exception: BaseException | None = None,
    stderr_text: str | None = None,
) -> str | None:
    if not agentic_result:
        return None
    status = str(agentic_result.get("status") or "").lower()
    if status in ("success", "ok", "completed"):
        return None

    error_type = ""
    error_code = ""
    error_text_parts: list[str] = []

    error_payload = agentic_result.get("error")
    if isinstance(error_payload, dict):
        error_type = str(error_payload.get("type") or error_payload.get("error") or "")
        error_code = str(
            error_payload.get("code")
            or error_payload.get("error_code")
            or error_payload.get("status")
            or ""
        )
        message = error_payload.get("message")
        if message:
            error_text_parts.append(str(message))
    elif isinstance(error_payload, str):
        error_text_parts.append(error_payload)

    errors_list = agentic_result.get("errors")
    if isinstance(errors_list, list):
        error_text_parts.extend([str(item) for item in errors_list])

    if stderr_text:
        error_text_parts.append(stderr_text)

    stderr_hash = _hash_text(" ".join(error_text_parts)) if error_text_parts else ""
    exception_class = exception.__class__.__name__ if exception else ""

    fingerprint_input = f"{error_type}{error_code}{exception_class}{stderr_hash}"
    if not fingerprint_input:
        return None
    return _hash_text(fingerprint_input)
