"""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 Any, 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", "ptrace", "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 GrpcConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    addr: str


class ServerTimeoutsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    read_timeout: str | None = None
    write_timeout: str | None = None
    max_request_size: str | None = None


class LoggingConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    level: str | None = None
    format: str | None = None
    output: str | None = None


class SessionsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    base_dir: str | None = None
    max_sessions: int | None = None
    default_timeout: str | None = None
    idle_timeout: str | None = None
    cleanup_interval: str | None = None


class AuditConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    sqlite_path: str | None = None
    batch_size: int | None = None
    flush_interval: str | None = None
    channel_size: int | None = None


class SandboxLimitsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    max_memory_mb: int | None = None
    max_cpu_percent: int | None = None
    max_processes: int | None = None


class FuseConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    deferred: bool | None = None
    deferred_marker_file: str | None = None
    deferred_enable_command: list[str] | None = None


class NetworkInterceptConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    intercept_mode: str | None = None
    proxy_listen_addr: str | None = None


class FileMonitorConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    enforce_without_fuse: bool | None = None
    intercept_metadata: bool | None = None
    openat_emulation: bool | None = None
    block_io_uring: bool | None = None


class SeccompDetailsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    execve: bool | None = None
    file_monitor: FileMonitorConfig | None = None


class CgroupsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None


class UnixSocketsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None


class PtraceTraceConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    execve: bool | None = None
    file: bool | None = None
    network: bool | None = None
    signal: bool | None = None


class PtracePerformanceConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    seccomp_prefilter: bool | None = None
    max_tracees: int | None = None
    max_hold_ms: int | None = None
    static_allow_file: bool | None = None
    static_allow_network: bool | None = None
    arg_level_filter: bool | None = None


class PtraceConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    attach_mode: Literal["children", "pid"] | None = None
    mask_tracer_pid: str | None = None
    trace: PtraceTraceConfig | None = None
    performance: PtracePerformanceConfig | None = None
    on_attach_failure: Literal["fail_open", "fail_closed"] | None = None


class ProxyConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    mode: str | None = None
    port: int | None = None
    providers: dict[str, str] | None = None


class DlpCustomPattern(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str
    display: str
    regex: str


class DlpConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    mode: str | None = None
    patterns: dict[str, bool] | None = None
    custom_patterns: list[DlpCustomPattern] | None = None


class PoliciesOverrideConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    dir: str | None = None
    default_policy: str | None = None


class PolicySigningConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    trust_store: str | None = None
    mode: Literal["enforce", "warn", "off"] | None = None


class ApprovalsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    mode: str | None = None
    timeout: str | None = None


class MetricsConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    path: str | None = None


class HealthConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    path: str | None = None
    readiness_path: str | None = None


class DevelopmentConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    disable_auth: bool | None = None
    verbose_errors: bool | None = None


class ProviderConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    enabled: bool | None = None
    priority: int | None = None
    timeout: str | None = None
    on_failure: Literal["warn", "deny", "allow", "approve"] | None = None
    api_key_env: str | None = None
    type: Literal["exec"] | None = None
    command: str | None = None
    options: dict[str, Any] | None = None


class PackageChecksConfig(BaseModel):
    model_config = ConfigDict(extra="forbid")
    scope: Literal["new_packages_only", "all_installs"] | None = None
    providers: dict[str, bool | ProviderConfig] | None = None


class ServerConfig(BaseModel):
    """Extended server configuration — mirrors TS ServerConfigOpts."""
    model_config = ConfigDict(extra="forbid")
    grpc: GrpcConfig | None = None
    server_timeouts: ServerTimeoutsConfig | None = None
    logging: LoggingConfig | None = None
    sessions: SessionsConfig | None = None
    audit: AuditConfig | None = None
    sandbox_limits: SandboxLimitsConfig | None = None
    allow_degraded: bool | None = None
    fuse: FuseConfig | None = None
    network_intercept: NetworkInterceptConfig | None = None
    seccomp_details: SeccompDetailsConfig | None = None
    cgroups: CgroupsConfig | None = None
    unix_sockets: UnixSocketsConfig | None = None
    ptrace: PtraceConfig | None = None
    env_inject: dict[str, str] | None = None
    proxy: ProxyConfig | None = None
    dlp: DlpConfig | None = None
    policies_override: PoliciesOverrideConfig | None = None
    policy_signing: PolicySigningConfig | None = None
    approvals: ApprovalsConfig | None = None
    metrics: MetricsConfig | None = None
    health: HealthConfig | None = None
    development: DevelopmentConfig | 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
    security_mode: SecurityMode | None = None
    """
    Override the detected security mode. Only used with installStrategy 'running',
    where ``agentsh detect`` is skipped (it would conflict with the running server).
    Default for 'running': 'full'. Ignored for other install strategies.
    """
    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.
    """
    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"
    session_id: str | None = None
    """
    Existing agentsh session ID. Only used with installStrategy 'running'.
    If not provided, reads $AGENTSH_SESSION_ID from the sandbox environment.
    """
    threat_feeds: ThreatFeedsConfig | Literal[False] | None = None
    env: dict[str, str] | None = None
    skip_shim: bool = False
    package_checks: PackageChecksConfig | Literal[False] | None = None
    server_config: ServerConfig | None = None
