from __future__ import annotations

import json
import os
import signal
import subprocess
import tempfile
from pathlib import Path
from typing import Any

from .base import BackendResult, TranscriptionRequest


class CodexExecBackend:
    name = "codex-exec"

    def __init__(self, executable: str = "codex") -> None:
        self.executable = executable

    def transcribe(self, request: TranscriptionRequest) -> BackendResult:
        with tempfile.TemporaryDirectory(prefix="crowley-codex-") as temp_directory:
            temp = Path(temp_directory)
            final_path = temp / "last-message.json"
            command = [
                self.executable,
                "-a",
                "never",
                "exec",
                "--ephemeral",
                "--ignore-user-config",
                "--ignore-rules",
                "--sandbox",
                "read-only",
                "--skip-git-repo-check",
                "--color",
                "never",
                "-C",
                str(temp),
                "--model",
                request.model,
                "-c",
                f'model_reasoning_effort="{request.reasoning_effort}"',
                "--image",
                str(request.image_path.resolve()),
                "--output-schema",
                str(request.output_schema_path.resolve()),
                "--json",
                "--output-last-message",
                str(final_path),
                "-",
            ]
            recorded_command = [
                "codex",
                "-a",
                "never",
                "exec",
                "--ephemeral",
                "--ignore-user-config",
                "--ignore-rules",
                "--sandbox",
                "read-only",
                "--skip-git-repo-check",
                "--model",
                request.model,
                "-c",
                f'model_reasoning_effort="{request.reasoning_effort}"',
                "--image",
                f"<source>/{request.image_path.name}",
                "--output-schema",
                request.output_schema_path.name,
                "--json",
                "--output-last-message",
                "<temporary>/last-message.json",
                "-",
            ]
            process = subprocess.Popen(
                command,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                start_new_session=True,
            )
            try:
                stdout, stderr = process.communicate(
                    input=request.prompt, timeout=request.timeout_seconds
                )
            except subprocess.TimeoutExpired as exc:
                # Codex is launched through a Node wrapper. Killing only the wrapper
                # can leave the native CLI or code-mode host holding the captured
                # pipes open. Terminate the isolated process group so a timeout is
                # finite and retryable.
                os.killpg(process.pid, signal.SIGTERM)
                try:
                    process.communicate(timeout=5)
                except subprocess.TimeoutExpired:
                    os.killpg(process.pid, signal.SIGKILL)
                    process.communicate()
                return BackendResult(
                    success=False,
                    exit_code=None,
                    final_message=None,
                    stdout=_as_text(exc.stdout),
                    stderr=_as_text(exc.stderr),
                    command=recorded_command,
                    error=f"Codex execution timed out after {request.timeout_seconds} seconds",
                )
            final_message = (
                final_path.read_text(encoding="utf-8") if final_path.is_file() else None
            )
            metadata = _event_metadata(stdout)
            success = process.returncode == 0 and final_message is not None
            return BackendResult(
                success=success,
                exit_code=process.returncode,
                final_message=final_message,
                stdout=stdout,
                stderr=stderr,
                command=recorded_command,
                metadata=metadata,
                error=None if success else "Codex did not produce a successful final response",
            )


def _as_text(value: str | bytes | None) -> str:
    if value is None:
        return ""
    if isinstance(value, bytes):
        return value.decode("utf-8", errors="replace")
    return value


def _event_metadata(stdout: str) -> dict[str, Any]:
    event_types: dict[str, int] = {}
    thread_id: str | None = None
    usage: dict[str, Any] | None = None
    for line in stdout.splitlines():
        try:
            event = json.loads(line)
        except json.JSONDecodeError:
            continue
        event_type = str(event.get("type", "unknown"))
        event_types[event_type] = event_types.get(event_type, 0) + 1
        if event_type == "thread.started":
            thread_id = event.get("thread_id")
        if "usage" in event and isinstance(event["usage"], dict):
            usage = event["usage"]
    return {"thread_id": thread_id, "event_types": event_types, "usage": usage}
