from typing import Any

from ..models import CollectorError, InstanceDescriptor, JobDescriptor
from ..normalizer import observation_from_signals
from .base import CollectorContext, CommandRunner, SubprocessCommandRunner, result


class DockerCollector:
    """Read-only Docker container status collector."""

    name = "docker"
    version = "0.2"

    def __init__(self, runner: CommandRunner | None = None) -> None:
        self.runner = runner or SubprocessCommandRunner()

    def collect(self, context: CollectorContext):  # type: ignore[no-untyped-def]
        observations = []
        errors: list[CollectorError] = []
        for definition in context.config.definitions:
            container = str(definition["container_name"])
            job = JobDescriptor(str(definition["job_key"]), str(definition.get("display_name", container)), "docker",
                                str(definition.get("job_type", "worker")), stale_after_seconds=definition.get("stale_after_seconds"),
                                timeout_seconds=context.config.timeout_seconds)
            worker_key = str(definition.get("worker_key", job.job_key))
            instance = InstanceDescriptor(job, context.host, "docker", container, instance_name=container,
                                          container_name=container, worker_key=worker_key, location_type="vps",
                                          location_name="VPS Docker")
            signals: dict[str, Any] = {"source": "docker", "container_name": container, "container_exists": False}
            try:
                code, stdout, stderr = self.runner.run(
                    ["docker", "inspect", "--format", "{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{end}}", container],
                    context.config.timeout_seconds,
                )
                if code != 0:
                    errors.append(CollectorError("docker_error", stderr.strip() or f"docker exited {code}", instance.instance_key))
                    signals["unavailable"] = True
                else:
                    state, health = (stdout.strip().split("|", 1) + [""])[:2]
                    signals.update({"container_exists": True, "container_state": state, "container_health": health or None,
                                    "reported_state": "running" if state == "running" else "stopped"})
                    if state != "running" or health == "unhealthy":
                        signals["reported_health"] = "failed"
                    elif health == "starting":
                        signals["reported_health"] = "degraded"
                    else:
                        signals["reported_health"] = "healthy"
            except (OSError, ValueError) as exc:
                errors.append(CollectorError(type(exc).__name__, str(exc), instance.instance_key))
                signals["unavailable"] = True
            observations.append(observation_from_signals(job, instance, None, signals, context.now))
        return result(self.name, self.version, context.now, observations, errors, {"read_only": True, "commands": ["docker inspect"]})
