"""Core types — protocols, result types, and config models.

See spec Section 5 (5.1 through 5.5) for all type definitions.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal, Protocol, runtime_checkable

from pydantic import BaseModel, ConfigDict

from agentsh_secure_sandbox.policies.schema import PolicyDefinition

# ─── 5.1 Result Types ──────────────────────────────────


@dataclass(frozen=True, slots=True)
class ExecResult:
    stdout: str
    stderr: str
    exit_code: int


@dataclass(frozen=True, slots=True)
class WriteFileSuccess:
    success: Literal[True]
    path: str


@dataclass(frozen=True, slots=True)
class WriteFileFailure:
    success: Literal[False]
    path: str
    error: str


WriteFileResult = WriteFileSuccess | WriteFileFailure


@dataclass(frozen=True, slots=True)
class ReadFileSuccess:
    success: Literal[True]
    path: str
    content: str


@dataclass(frozen=True, slots=True)
class ReadFileFailure:
    success: Literal[False]
    path: str
    error: str


ReadFileResult = ReadFileSuccess | ReadFileFailure

# ─── 5.2 Enums ─────────────────────────────────────────

SecurityMode = Literal["full", "landlock", "landlock-only", "minimal"]
InstallStrategy = Literal["preinstalled", "download", "upload", "running"]

# ─── 5.3 SandboxAdapter Protocol ───────────────────────


@dataclass
class ExecOpts:
    cwd: str | None = None
    sudo: bool = False
    detached: bool = False


@runtime_checkable
class SandboxAdapter(Protocol):
    """
    Minimal interface to a sandbox environment.

    During provisioning: used for installing binary, starting server,
    creating session, health checks.

    At runtime: used as transport for `agentsh exec $SID -- <command>`.
    """

    async def exec(
        self,
        cmd: str,
        args: list[str] | None = None,
        opts: ExecOpts | None = None,
    ) -> ExecResult: ...

    async def write_file(
        self,
        path: str,
        content: str | bytes,
        *,
        sudo: bool = False,
    ) -> None: ...

    async def read_file(self, path: str) -> str: ...

    async def stop(self) -> None: ...

    async def file_exists(self, path: str) -> bool: ...


class SandboxAdapterBase:
    """
    Optional base class with default implementations.
    Adapters can inherit from this or just satisfy SandboxAdapter protocol.
    """

    async def exec(
        self,
        cmd: str,
        args: list[str] | None = None,
        opts: ExecOpts | None = None,
    ) -> ExecResult:
        raise NotImplementedError

    async def write_file(
        self,
        path: str,
        content: str | bytes,
        *,
        sudo: bool = False,
    ) -> None:
        raise NotImplementedError

    async def read_file(self, path: str) -> str:
        raise NotImplementedError

    async def stop(self) -> None:
        pass

    async def file_exists(self, path: str) -> bool:
        result = await self.exec("test", ["-f", path])
        return result.exit_code == 0


# ─── 5.4 SecuredSandbox Protocol ───────────────────────


@runtime_checkable
class SecuredSandbox(Protocol):
    """
    A sandbox with agentsh policy enforcement active.

    Every operation routes through agentsh. The agent cannot
    bypass policy from userspace.
    """

    @property
    def session_id(self) -> str: ...

    @property
    def security_mode(self) -> SecurityMode: ...

    async def exec(
        self,
        command: str,
        *,
        cwd: str | None = None,
        timeout: float | None = None,
    ) -> ExecResult: ...

    async def write_file(self, path: str, content: str | bytes) -> WriteFileResult: ...

    async def read_file(self, path: str) -> ReadFileResult: ...

    async def stop(self) -> None: ...


# ─── 5.5 SecureConfig ──────────────────────────────────


class ThreatFeed(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str
    url: str
    format: Literal["hostfile", "domain-list"]
    refresh_interval: str = "6h"


class ThreatFeedsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    feeds: list[ThreatFeed]
    action: Literal["deny", "audit"] = "deny"
    allowlist: list[str] | None = None


class SecureConfig(BaseModel):
    """Configuration for secure_sandbox()."""

    model_config = ConfigDict(extra="forbid")

    policy: PolicyDefinition | None = None
    workspace: str = "/workspace"
    watchtower: str | None = None
    install_strategy: InstallStrategy = "download"
    agentsh_version: str | None = None  # Default: pinned per release
    agentsh_arch: Literal["linux_amd64", "linux_arm64"] | None = None
    agentsh_binary_url: str | None = None
    agentsh_checksum: str | None = None
    skip_integrity_check: bool = False
    minimum_security_mode: SecurityMode | None = None
    real_paths: bool | None = None
    """
    FUSE-based path canonicalization.
    - None (default): Auto-detected during provisioning. Enabled when
      security mode is ``full`` (FUSE available), disabled otherwise.
    - True/False: Override auto-detection.
    """
    enforce_redirects: bool = False
    trace_parent: str | Literal[False] | None = None
    """
    Trace context propagation mode.
    - None (default): Auto-resolve. Tries active OTel span first (works
      automatically with Logfire), then TRACEPARENT env var, then skips.
    - A W3C traceparent string: Use verbatim.
    - False: Disable trace propagation entirely.
    See TRACE-PROPAGATION.md for full design.
    """
    policy_name: str = "policy"
    threat_feeds: ThreatFeedsConfig | Literal[False] | None = None
    env: dict[str, str] | None = None
