"""Disabled canonical registration for owner/customer-only Telegram bootstrap."""

from __future__ import annotations

import importlib
import os
import stat
from dataclasses import dataclass
from datetime import date, time
from pathlib import Path
from types import ModuleType
from typing import cast

from gateway.platforms.dualcoach_profile_package import (
    DualCoachProfilePackage,
    ProfilePackageResolutionError,
)
from gateway.platforms.telegram_customer_bootstrap import (
    BootstrapError,
    BootstrapState,
    Role,
    RoomBootstrapSession,
    RoomBootstrapStore,
)
from gateway.platforms.telegram_nutrition_addresses import (
    parse_customer_private_delivery_address,
)


class RegistrationConflict(BootstrapError):
    code = "CONFLICT"


@dataclass(frozen=True, slots=True)
class CustomerConsentRoute:
    user_id: str
    chat_id: str
    topic_id: str

    @property
    def key(self) -> tuple[str, str, str]:
        return self.user_id, self.chat_id, self.topic_id


@dataclass(frozen=True, slots=True)
class DisabledRegistrationReceipt:
    session: RoomBootstrapSession
    customer_key: str
    consent_route: CustomerConsentRoute
    reconciled: bool


class TelegramCustomerBootstrapRegistration:
    """Register one claimed customer DM as disabled; grant no activation."""

    def __init__(
        self,
        profile_root: Path | str,
        store: RoomBootstrapStore,
        *,
        package_root: Path | str | None = None,
    ) -> None:
        root = Path(profile_root)
        customers = root / "customers"
        registry = customers / "registry.json"
        for directory in (root, customers):
            if directory.is_symlink():
                raise BootstrapError("registration directory is a symlink")
            try:
                info = directory.stat(follow_symlinks=False)
            except OSError as error:
                raise BootstrapError(
                    "registration profile root is unavailable"
                ) from error
            if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.geteuid():
                raise BootstrapError("registration directory is unsafe")
            directory.chmod(0o700)
        try:
            registry_info = registry.stat(follow_symlinks=False)
        except OSError as error:
            raise BootstrapError("registration registry is unavailable") from error
        if (
            registry.is_symlink()
            or not stat.S_ISREG(registry_info.st_mode)
            or registry_info.st_uid != os.geteuid()
            or registry_info.st_nlink != 1
        ):
            raise BootstrapError("registration registry is unsafe")
        registry.chmod(0o600)
        if root.is_symlink() or not root.exists() or not root.is_dir():
            raise BootstrapError("registration profile root is unavailable")
        if not isinstance(store, RoomBootstrapStore):
            raise TypeError("registration requires the customer bootstrap store")
        self.profile_root = root.absolute()
        configured_package = package_root or os.environ.get(
            "DUALCOACH_PROFILE_PACKAGE"
        )
        try:
            self.package = DualCoachProfilePackage.from_root(
                configured_package
                or self.profile_root / "workspace" / "checkin_cli"
            )
        except ProfilePackageResolutionError as exc:
            raise BootstrapError(str(exc)) from exc
        self.store = store

    def handoff_rehearsal_customer(
        self,
        session: RoomBootstrapSession,
    ) -> DisabledRegistrationReceipt:
        live = self._live_session(session)
        customer = live.role_claim(Role.CUSTOMER)
        draft = live.customer_draft
        if (
            live.state is not BootstrapState.REGISTERING
            or customer is None
            or len(live.role_claims) != 1
            or customer.user_id != customer.chat_id
            or customer.topic_id != "0"
            or customer.user_id != draft.customer_user_id
            or customer.user_id == live.owner_id
        ):
            raise RegistrationConflict(
                "customer registration authority is unavailable"
            )
        return self._handoff_private_customer(live)

    def _handoff_private_customer(
        self,
        session: RoomBootstrapSession,
    ) -> DisabledRegistrationReceipt:
        admin, coaching = self._profile_modules()
        registry_path = admin._resolve_registry_path(self.profile_root)
        with admin.profile_authority_lock(self.profile_root):
            live = self._live_session(session)
            if live.state is not BootstrapState.REGISTERING:
                raise RegistrationConflict("registration generation is stale")
            customer = live.role_claim(Role.CUSTOMER)
            if customer is None:
                raise RegistrationConflict("customer route is unavailable")
            payload = live.customer_draft.to_dict()
            payload.update(
                {
                    "user_id": customer.user_id,
                    "chat_id": customer.chat_id,
                    "topic_id": customer.topic_id,
                }
            )
            document = admin._read(registry_path)
            if self._owner_key(document.owner)[0] != live.owner_id:
                raise RegistrationConflict("canonical owner authority changed")
            existing = self._customer(document, live.customer_key)
            reconciled = existing is not None
            if existing is None:
                admin.register_customer(
                    registry_path,
                    self._customer_draft(admin, coaching, payload),
                )
            persisted_document = admin._read(registry_path)
            persisted = self._customer(persisted_document, live.customer_key)
            if (
                persisted is None
                or not self._is_exact_disabled_row(persisted, payload)
            ):
                raise RegistrationConflict("disabled registration mismatch")
            completed = self.store.transition(
                live.session_id,
                expected_generation=live.generation,
                target=BootstrapState.AWAITING_CONSENT,
            )
            completed = self.store.bind_private_owner(
                completed.session_id,
                owner_id=self._owner_key(persisted_document.owner)[0],
                expected_generation=completed.generation,
            )
            return DisabledRegistrationReceipt(
                session=completed,
                customer_key=completed.customer_key,
                consent_route=CustomerConsentRoute(
                    customer.user_id,
                    customer.chat_id,
                    customer.topic_id,
                ),
                reconciled=reconciled,
            )

    def _profile_modules(self) -> tuple[ModuleType, ModuleType]:
        try:
            admin = self.package.resolve(
                "checkin_cli.customer_admin",
                importer=importlib.import_module,
            )
            coaching = self.package.resolve(
                "checkin_cli.customer_coaching",
                importer=importlib.import_module,
            )
        except ProfilePackageResolutionError as exc:
            raise BootstrapError(str(exc)) from exc
        return admin, coaching

    def _live_session(
        self,
        supplied: RoomBootstrapSession,
    ) -> RoomBootstrapSession:
        try:
            live = self.store.get(supplied.session_id)
        except BootstrapError as exc:
            raise RegistrationConflict("bootstrap session is unavailable") from exc
        if live != supplied:
            raise RegistrationConflict("bootstrap session evidence is stale")
        return live

    @staticmethod
    def _customer(document: object, customer_key: str) -> object | None:
        return next(
            (
                item
                for item in getattr(document, "customers", ())
                if getattr(item, "customer_key", None) == customer_key
            ),
            None,
        )

    @staticmethod
    def _owner_key(owner: object) -> tuple[str, str, str]:
        return (
            str(getattr(owner, "user_id", "")),
            str(getattr(owner, "chat_id", "")),
            str(getattr(owner, "topic_id", "")),
        )

    @staticmethod
    def _customer_draft(
        admin: ModuleType,
        coaching: ModuleType,
        payload: dict[str, object],
    ) -> object:
        del coaching
        values = dict(payload)
        values.pop("customer_user_id", None)
        values["starts_on"] = date.fromisoformat(str(values["starts_on"]))
        values["daily_time"] = time.fromisoformat(str(values["daily_time"]))
        values["meals"] = tuple(cast(list[object], values["meals"]))
        for name in (
            "dietary_restrictions",
            "allergies",
            "food_preferences",
            "supplements",
        ):
            values[name] = tuple(cast(list[object], values[name]))
        customer = parse_customer_private_delivery_address(
            {
                "user_id": values["user_id"],
                "chat_id": values["chat_id"],
                "topic_id": values["topic_id"],
            }
        )
        values.update(
            {
                "user_id": str(customer.user_id),
                "chat_id": str(customer.chat_id),
                "topic_id": str(customer.topic_id),
            }
        )
        return admin.CustomerDraft(**values)

    @staticmethod
    def _is_exact_disabled_row(
        row: object,
        payload: dict[str, object],
    ) -> bool:
        telegram = getattr(row, "telegram", None)
        schedule = getattr(row, "schedule", None)
        profile = getattr(row, "profile", None)
        plan = getattr(row, "plan", None)
        weeks = tuple(getattr(plan, "weeks", ()))
        expected_meals = tuple(cast(list[object], payload["meals"]))
        expected_restrictions = tuple(
            cast(list[object], payload["dietary_restrictions"])
        )
        expected_allergies = tuple(cast(list[object], payload["allergies"]))
        expected_preferences = tuple(
            cast(list[object], payload["food_preferences"])
        )
        expected_supplements = tuple(cast(list[object], payload["supplements"]))
        return bool(
            getattr(row, "enabled", None) is False
            and getattr(row, "customer_key", None) == payload["customer_key"]
            and getattr(row, "display_name", None) == payload["display_name"]
            and telegram is not None
            and telegram.key
            == (payload["user_id"], payload["chat_id"], payload["topic_id"])
            and getattr(schedule, "daily_time", None)
            == time.fromisoformat(str(payload["daily_time"]))
            and getattr(schedule, "weekly_weekday", None)
            == payload["weekly_weekday"]
            and getattr(schedule, "monthly_day", None) == payload["monthly_day"]
            and getattr(plan, "starts_on", None)
            == date.fromisoformat(str(payload["starts_on"]))
            and len(weeks) == 12
            and all(
                week.week == index
                and week.calories_kcal == payload["calories_kcal"]
                and week.protein_g == payload["protein_g"]
                and tuple(week.meal_structure) == expected_meals
                and week.carbohydrate_g == payload["carbohydrate_g"]
                and week.fat_g == payload["fat_g"]
                and week.water_liters == payload["water_liters"]
                for index, week in enumerate(weeks, 1)
            )
            and getattr(profile, "primary_goal", None) == payload["primary_goal"]
            and tuple(getattr(profile, "dietary_restrictions", ()))
            == expected_restrictions
            and tuple(getattr(profile, "allergies", ())) == expected_allergies
            and tuple(getattr(profile, "food_preferences", ()))
            == expected_preferences
            and tuple(getattr(profile, "supplements", ()))
            == expected_supplements
        )


__all__ = [
    "CustomerConsentRoute",
    "DisabledRegistrationReceipt",
    "RegistrationConflict",
    "TelegramCustomerBootstrapRegistration",
]
