"""Canonical operator command for preparing one customer invitation."""

from __future__ import annotations

import json
import os
import secrets
import stat
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Final
from zoneinfo import ZoneInfo

import yaml
from pydantic import JsonValue, TypeAdapter

from .telegram_customer_bootstrap import (
    BootstrapError,
    CustomerDraft,
    RoomBootstrapStore,
    load_customer_draft,
    room_bootstrap_state_dir,
)

_OBJECT: Final = TypeAdapter(dict[str, JsonValue])


class CustomerInviteError(BootstrapError):
    """The operator invitation boundary rejected its inputs."""


@dataclass(frozen=True, slots=True)
class CustomerInviteResult:
    customer_key: str
    customer_link: str
    draft_digest: str
    expires_at: str
    generation: int
    session_id: str

    def to_dict(self) -> dict[str, JsonValue]:
        return {
            "customer_key": self.customer_key,
            "customer_link": self.customer_link,
            "draft_digest": self.draft_digest,
            "expires_at": self.expires_at,
            "generation": self.generation,
            "session_id": self.session_id,
        }


def _mapping(value: JsonValue | None, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise CustomerInviteError(f"{label} is invalid")
    return value


def _private_profile(profile_root: Path) -> None:
    try:
        info = profile_root.stat(follow_symlinks=False)
    except OSError as error:
        raise CustomerInviteError("profile root is unavailable") from error
    if (
        not profile_root.is_absolute()
        or profile_root.is_symlink()
        or not stat.S_ISDIR(info.st_mode)
        or stat.S_IMODE(info.st_mode) != 0o700
        or info.st_uid != os.geteuid()
    ):
        raise CustomerInviteError("profile root is unsafe")


def _private_directory(path: Path, label: str) -> None:
    try:
        info = path.stat(follow_symlinks=False)
    except OSError as error:
        raise CustomerInviteError(f"{label} is unavailable") from error
    if (
        path.is_symlink()
        or not stat.S_ISDIR(info.st_mode)
        or stat.S_IMODE(info.st_mode) != 0o700
        or info.st_uid != os.geteuid()
    ):
        raise CustomerInviteError(f"{label} is unsafe")


def _private_file(path: Path, label: str) -> None:
    try:
        info = path.stat(follow_symlinks=False)
    except OSError as error:
        raise CustomerInviteError(f"{label} is unavailable") from error
    if (
        path.is_symlink()
        or not stat.S_ISREG(info.st_mode)
        or stat.S_IMODE(info.st_mode) != 0o600
        or info.st_uid != os.geteuid()
        or info.st_nlink != 1
    ):
        raise CustomerInviteError(f"{label} is unsafe")


def _operator_binding(profile_root: Path, customer_key: str) -> tuple[str, str]:
    _private_file(profile_root / "config.yaml", "profile config")
    _private_file(
        profile_root / "customers/registry.json",
        "customer registry",
    )
    config = _OBJECT.validate_python(
        yaml.safe_load((profile_root / "config.yaml").read_text(encoding="utf-8"))
    )
    platforms = _mapping(config.get("platforms"), "platforms")
    telegram = _mapping(platforms.get("telegram"), "telegram")
    extra = _mapping(telegram.get("extra"), "telegram extra")
    preflight = _mapping(extra.get("production_preflight"), "production preflight")
    bot_username = preflight.get("expected_bot_username")
    registry = _OBJECT.validate_json(
        (profile_root / "customers/registry.json").read_bytes()
    )
    owner = _mapping(registry.get("owner"), "registry owner")
    owner_id = owner.get("user_id")
    customers = registry.get("customers")
    policy = _mapping(registry.get("admission_policy"), "admission policy")
    capacity = policy.get("max_enabled_customers")
    if (
        not isinstance(bot_username, str)
        or not isinstance(owner_id, str)
        or not isinstance(customers, list)
        or isinstance(capacity, bool)
        or not isinstance(capacity, int)
    ):
        raise CustomerInviteError("operator binding is invalid")
    rows = [_mapping(customer, "registry customer") for customer in customers]
    if any(row.get("customer_key") == customer_key for row in rows):
        raise CustomerInviteError("customer key already exists")
    if sum(row.get("enabled") is True for row in rows) >= capacity:
        raise CustomerInviteError("customer capacity is full")
    return bot_username, owner_id


def issue_customer_invite(
    profile_root: Path,
    draft_path: Path,
) -> CustomerInviteResult:
    """Prepare one 24-hour, single-use customer invitation."""
    _private_profile(profile_root)
    _private_directory(profile_root / "data", "profile data")
    _private_directory(profile_root / "customers", "profile customers")
    draft = load_customer_draft(draft_path)
    bot_username, owner_id = _operator_binding(profile_root, draft.customer_key)
    prepared = RoomBootstrapStore(
        room_bootstrap_state_dir(profile_root)
    ).prepare_rehearsal_customer_invite(
        draft,
        bot_username=bot_username,
        owner_id=owner_id,
    )
    return CustomerInviteResult(
        customer_key=draft.customer_key,
        customer_link=prepared.customer_link,
        draft_digest=draft.digest,
        expires_at=prepared.expires_at.isoformat(),
        generation=prepared.session.generation,
        session_id=prepared.session.session_id,
    )


def issue_first_claim_invite(
    profile_root: Path,
) -> CustomerInviteResult:
    """Prepare one unbound intake link whose first private claimant wins."""
    _private_profile(profile_root)
    _private_directory(profile_root / "data", "profile data")
    _private_directory(profile_root / "customers", "profile customers")
    customer_key = "lead_" + secrets.token_hex(8)
    bot_username, owner_id = _operator_binding(profile_root, customer_key)
    draft = CustomerDraft(
        customer_key=customer_key,
        display_name="신규 고객",
        starts_on=datetime.now(ZoneInfo("Asia/Seoul")).date().isoformat(),
        daily_time="08:00",
        weekly_weekday=0,
        monthly_day=1,
        calories_kcal=2000,
        protein_g=100,
        meals=("아침", "점심", "저녁"),
        primary_goal="온보딩 대기",
        customer_user_id=None,
    )
    prepared = RoomBootstrapStore(
        room_bootstrap_state_dir(profile_root)
    ).prepare_rehearsal_customer_invite(
        draft,
        bot_username=bot_username,
        owner_id=owner_id,
        first_claim=True,
    )
    return CustomerInviteResult(
        customer_key=draft.customer_key,
        customer_link=prepared.customer_link,
        draft_digest=draft.digest,
        expires_at=prepared.expires_at.isoformat(),
        generation=prepared.session.generation,
        session_id=prepared.session.session_id,
    )


def encode_customer_invite(result: CustomerInviteResult) -> str:
    """Encode the machine-consumed CLI result."""
    return json.dumps(result.to_dict(), sort_keys=True, separators=(",", ":"))
