#!/usr/bin/env python3
# /// script
# requires-python = ">=3.12,<3.13"
# ///
"""Archive stale staff-membership authority before NutriCoach invite creation."""

from __future__ import annotations

import argparse
import os
import stat
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence

from first_customer_invite_contract import (
    ControllerError,
    JsonValue,
    exclusive_json,
    json_object,
    require_private_file,
    sha256_file,
)


OLD_BOT = "dual_coach_pilot_test_bot"
NEW_BOT = "nutricoach_kr_bot"
SOURCE_RELATIVE = Path("data/onboarding/telegram-staff-membership-v1")
ARCHIVE_PARENT_RELATIVE = Path("data/onboarding-archive")


@dataclass(frozen=True, slots=True)
class StaffResetPermission:
    profile: Path
    source_root: Path
    archive_root: Path
    receipt_path: Path
    events_sha256: str
    authorization_sha256: str


def _mapping(value: JsonValue | None, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise ControllerError(f"{label} must be an object")
    return value


def _service_stopped() -> bool:
    result = subprocess.run(
        [
            "systemctl",
            "--user",
            "show",
            "hermes-gateway-dualcoachtest.service",
            "-p",
            "MainPID",
            "-p",
            "ActiveState",
            "-p",
            "SubState",
        ],
        check=False,
        capture_output=True,
        text=True,
    )
    fields = dict(
        line.split("=", 1) for line in result.stdout.splitlines() if "=" in line
    )
    return result.returncode == 0 and fields == {
        "MainPID": "0",
        "ActiveState": "inactive",
        "SubState": "dead",
    }


def _permission(path: Path) -> StaffResetPermission:
    require_private_file(path)
    value = json_object(path)
    authorization_path = Path(str(value.get("authorization_path"))).resolve()
    authorization = json_object(authorization_path)
    old_bot = _mapping(authorization.get("old_bot"), "old bot")
    new_bot = _mapping(authorization.get("new_bot"), "new bot")
    if not (
        value.get("schema") == "dualcoach-branding-staff-reset-permission-v1"
        and value.get("authorization_sha256") == sha256_file(authorization_path)
        and value.get("script_sha256") == sha256_file(Path(__file__).resolve())
        and authorization.get("status") == "AUTHORIZED_NUTRICOACH_BOT_MIGRATION"
        and old_bot.get("username") == OLD_BOT
        and new_bot.get("username") == NEW_BOT
    ):
        raise ControllerError("staff reset permission binding mismatch")
    try:
        permission = StaffResetPermission(
            profile=Path(str(value["profile"])).resolve(),
            source_root=Path(str(value["source_root"])).resolve(),
            archive_root=Path(str(value["archive_root"])).resolve(),
            receipt_path=Path(str(value["receipt_path"])).resolve(),
            events_sha256=str(value["events_sha256"]),
            authorization_sha256=str(value["authorization_sha256"]),
        )
    except KeyError as exc:
        raise ControllerError("staff reset permission is malformed") from exc
    if (
        permission.source_root != permission.profile / SOURCE_RELATIVE
        or permission.archive_root.parent
        != permission.profile / ARCHIVE_PARENT_RELATIVE
        or permission.archive_root.exists()
        or permission.receipt_path.exists()
    ):
        raise ControllerError("staff reset path binding mismatch")
    return permission


def _validate(permission: StaffResetPermission) -> None:
    if not _service_stopped():
        raise ControllerError("Gateway must be inactive before staff reset")
    events = permission.source_root / "events.jsonl"
    require_private_file(events)
    if (
        stat.S_IMODE(permission.source_root.stat().st_mode) != 0o700
        or sha256_file(events) != permission.events_sha256
    ):
        raise ControllerError("staff authority binding mismatch")
    registry = json_object(permission.profile / "customers/registry.json")
    customers = registry.get("customers")
    if not isinstance(customers, list):
        raise ControllerError("customer baseline is not clean")
    for customer in customers:
        if not isinstance(customer, dict):
            raise ControllerError("customer baseline is not clean")
        consent = _mapping(
            customer.get("ai_processing_consent"),
            "AI processing consent",
        )
        if customer.get("enabled") is not False or consent.get("granted") is not False:
            raise ControllerError("customer baseline is not clean")


def reset(permission: StaffResetPermission) -> None:
    _validate(permission)
    parent = permission.archive_root.parent
    parent.mkdir(mode=0o700, exist_ok=True)
    parent.chmod(0o700)
    moved = False
    try:
        os.rename(permission.source_root, permission.archive_root)
        moved = True
        exclusive_json(
            permission.receipt_path,
            {
                "schema": "dualcoach-branding-staff-reset-receipt-v1",
                "status": "PASS_ARCHIVED",
                "old_bot_username": OLD_BOT,
                "new_bot_username": NEW_BOT,
                "events_sha256": permission.events_sha256,
                "archive_root": str(permission.archive_root),
                "authorization_sha256": permission.authorization_sha256,
            },
        )
    except OSError:
        if moved:
            os.rename(permission.archive_root, permission.source_root)
        raise


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--permission", type=Path, required=True)
    args = parser.parse_args(argv)
    try:
        reset(_permission(args.permission))
    except (ControllerError, OSError, KeyError, TypeError, ValueError) as exc:
        sys.stderr.write(f"FAIL: {exc}\n")
        return 2
    print('{"status":"PASS_ARCHIVED"}')
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
