# retoor <retoor@molodetz.nl>

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Awaitable, Callable, Optional

LogCallback = Callable[[str], Awaitable[None]]

WORKSPACE_MOUNT = "/app"
WORKSPACE_STATE_MOUNT = "/home/pravda/.workspace-state"


@dataclass
class PortMapping:
    host: int
    container: int
    proto: str = "tcp"


@dataclass
class Mount:
    host: str
    container: str
    mode: str = "rw"


@dataclass
class RunSpec:
    image: str
    name: str
    labels: dict = field(default_factory=dict)
    env: dict = field(default_factory=dict)
    cpu_limit: str = ""
    mem_limit: str = ""
    ports: list = field(default_factory=list)
    mounts: list = field(default_factory=list)
    restart_policy: str = "no"
    command: list = field(default_factory=list)


@dataclass
class BuildResult:
    success: bool
    image_id: str = ""
    error: str = ""


@dataclass
class ExecResult:
    exit_code: int
    output: str = ""


@dataclass
class StatsSample:
    cpu_pct: float = 0.0
    mem_bytes: int = 0
    mem_pct: float = 0.0
    net_rx: int = 0
    net_tx: int = 0
    blk_read: int = 0
    blk_write: int = 0


@dataclass
class PsRow:
    container_id: str
    name: str
    state: str
    status: str
    exit_code: Optional[int]
    labels: dict = field(default_factory=dict)


class Backend(ABC):
    @abstractmethod
    async def build(
        self,
        *,
        context_dir: str,
        dockerfile_text: str,
        tags: list,
        on_log: Optional[LogCallback] = None,
        network: str = "",
    ) -> BuildResult: ...

    @abstractmethod
    async def run(self, spec: RunSpec) -> str: ...

    @abstractmethod
    async def start(self, cid: str) -> None: ...

    @abstractmethod
    async def stop(self, cid: str, timeout: int = 10) -> None: ...

    @abstractmethod
    async def restart(self, cid: str) -> None: ...

    @abstractmethod
    async def pause(self, cid: str) -> None: ...

    @abstractmethod
    async def unpause(self, cid: str) -> None: ...

    @abstractmethod
    async def rm(self, cid: str, force: bool = False) -> None: ...

    @abstractmethod
    async def exec(
        self,
        cid: str,
        cmd: list,
        *,
        tty: bool = False,
        on_log: Optional[LogCallback] = None,
    ) -> ExecResult: ...

    @abstractmethod
    async def logs(
        self,
        cid: str,
        *,
        follow: bool = False,
        tail: int = 200,
        on_log: Optional[LogCallback] = None,
    ) -> None: ...

    @abstractmethod
    async def stats_once(self, cids: list) -> dict: ...

    @abstractmethod
    async def ps(self, *, label_filter: str = "devplace.instance") -> list: ...

    @abstractmethod
    async def inspect(self, cid: str) -> dict: ...

    @abstractmethod
    async def image_prune(self) -> None: ...

    @abstractmethod
    async def remove_image(self, ref: str, force: bool = False) -> None: ...

    @abstractmethod
    async def image_exists(self, ref: str) -> bool: ...
