"""Filesystem, profile, service, and unit snapshot operations."""

from __future__ import annotations
import os
import subprocess
from pathlib import Path
from typing import Final
from scripts.nutricoach_v150_live_upgrade_common import (
    JsonValue,
    LiveTarget,
    UpgradeDenied,
    canonical,
    list_at,
    object_at,
    sha256_bytes,
    sha256_file,
)

SERVICE_FIELDS: Final = (
    "ActiveState",
    "SubState",
    "MainPID",
    "ExecMainStartTimestampMonotonic",
)
VOLATILE_FILES: Final = frozenset({
    "auth.lock",
    "auth.json",
    "context_length_cache.yaml",
    "cron/jobs.json",
    "data/customer-activation-audit.jsonl",
    "data/customer-activation-journal.json",
    "data/nutrition-onboarding-projection-journal.jsonl",
    "data/onboarding/telegram-customer-bootstrap-v1/ledger.json",
    "data/onboarding/telegram-staff-membership-v1/events.jsonl",
    "data/scheduled-deliveries.jsonl",
    "data/topic59-maintenance-r71b/hold.json",
    "data/topic59-maintenance-r71b/hold.consuming.json",
    "data/topic59-maintenance-r71b/skip-audit.json",
    "data/topic59-maintenance-r71b/maintenance.lock",
    "gateway.pid",
    "gateway_state.json",
    "logs/agent.log",
    "logs/errors.log",
    "logs/gateway-exit-diag.log",
    "logs/gateway-shutdown-diag.log",
    "logs/gateway.log",
    "state.db",
    "state.db-shm",
    "state.db-wal",
})


def _volatile(path: Path) -> bool:
    text = path.as_posix()
    return (
        text in VOLATILE_FILES
        or text.endswith(".lock")
        or text.startswith("cache/")
        or text.startswith("cron/output/")
        or text.startswith("logs/")
        or text.startswith("data/onboarding/telegram-publication-outbox-v1/")
        or text.startswith("data/telegram-ingress-receipts-v1-")
    )


def tree_snapshot(root: Path, classify_volatile: bool = False) -> dict[str, JsonValue]:
    stable: list[JsonValue] = []
    volatile: list[JsonValue] = []
    for path in sorted(root.rglob("*")):
        if not (path.is_file() or path.is_symlink()):
            continue
        relative = path.relative_to(root)
        stat_result = path.lstat()
        entry: dict[str, JsonValue] = {
            "mode": stat_result.st_mode & 0o7777,
            "path": relative.as_posix(),
        }
        if path.is_symlink():
            entry.update({"kind": "symlink", "target": os.readlink(path)})
        else:
            entry.update({
                "kind": "file",
                "sha256": sha256_file(path),
                "size": stat_result.st_size,
            })
        (volatile if classify_volatile and _volatile(relative) else stable).append(
            entry
        )
    return {
        "stable": stable,
        "stable_digest": sha256_bytes(canonical(stable)),
        "volatile": volatile,
        "volatile_digest": sha256_bytes(canonical(volatile)),
    }


def profiles_snapshot(target: LiveTarget) -> dict[str, JsonValue]:
    stable: list[JsonValue] = []
    volatile: list[JsonValue] = []
    for profile in sorted(
        path for path in target.profiles_root.iterdir() if path.is_dir()
    ):
        snap = tree_snapshot(
            profile, profile.resolve() == target.profile_root.resolve()
        )
        for label, destination in (("stable", stable), ("volatile", volatile)):
            for raw in list_at(snap[label], label):
                destination.append({
                    **object_at(raw, "inventory"),
                    "profile": profile.name,
                })
    return {
        "stable": stable,
        "stable_digest": sha256_bytes(canonical(stable)),
        "volatile": volatile,
        "volatile_digest": sha256_bytes(canonical(volatile)),
    }


def service_state(name: str) -> dict[str, str]:
    command = ["systemctl", "--user", "show", name]
    command.extend(f"--property={field}" for field in SERVICE_FIELDS)
    result = subprocess.run(command, check=True, capture_output=True, text=True)
    values = dict(
        line.split("=", 1) for line in result.stdout.splitlines() if "=" in line
    )
    state = {field: values.get(field, "") for field in SERVICE_FIELDS}
    if state["ActiveState"] != "active" or state["SubState"] != "running":
        raise UpgradeDenied("service_not_active_running")
    return state


def contract_snapshot(target: LiveTarget) -> dict[str, JsonValue]:
    paths = [target.unit_file, *sorted(target.dropin_dir.glob("*.conf"))]
    credentials: list[Path] = []
    for dropin in paths[1:]:
        for line in dropin.read_text(encoding="utf-8").splitlines():
            if line.startswith("LoadCredential=") and ":" in line:
                credentials.append(Path(line.split(":", 1)[1]))
    paths.extend(sorted(set(credentials)))
    inventory: list[JsonValue] = [
        {"path": str(path), "sha256": sha256_file(path), "size": path.stat().st_size}
        for path in paths
    ]
    return {"digest": sha256_bytes(canonical(inventory)), "inventory": inventory}
