"""Production coordinator for one authority-fenced weekly-operations tick."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from pathlib import Path
from typing import Protocol, override, runtime_checkable

from checkin_cli.weekly_operations import WeeklyOperationRow, WeeklyOperationsError
from checkin_cli.weekly_operations_correlation import (
    CanonicalCheckinCorrelationTransaction,
    CorrelationAction,
    CorrelationRequest,
    CorrelationScope,
)
from checkin_cli.weekly_operations_lineage import canonical_prefix_pin
from checkin_cli.weekly_operations_owner_binding import (
    BoundWeeklySummaryForOwnerDraft,
    bind_weekly_summary_for_owner_draft,
)
from checkin_cli.weekly_operations_summary import build_weekly_operations_summary

from .nutrition_weekly_operations import (
    Topic59DayCardProjector,
    Topic59PublicationDisposition,
    Topic59PublicationRequest,
)
from .nutrition_weekly_operations_models import (
    Topic59ApprovalState,
    Topic59DayCard,
    Topic59DeliveryState,
    Topic59DraftState,
)
from .nutrition_weekly_owner_model import WeeklyExplanationModel
from .nutrition_weekly_owner_request import (
    WeeklyOwnerDraftRequest,
    WeeklyOwnerDraftResult,
)
from .nutrition_weekly_owner_storage import WeeklyOwnerStorageAuthority
from .nutrition_weekly_dispatcher_transport import (
    RevalidatingTopic59Transport,
    RevalidatingWeeklyModel,
    WeeklyOperationsHost,
)
from .nutrition_weekly_reminder_authority import (
    WeeklyOperationsTickSnapshot,
    WeeklyReminderAuthorityOwner,
)
from .telegram_weekly_reminder import send_weekly_operations_task


@runtime_checkable
class WeeklyOperationsTask(Protocol):
    @property
    def customer_key(self) -> str: ...

    @property
    def kind(self) -> str: ...

    @property
    def kst_day(self) -> date: ...


@runtime_checkable
class WeeklyOperationsCoordinator(Protocol):
    @property
    def profile_root(self) -> Path: ...

    def refresh_live_registry(self) -> bool: ...

    @property
    def weekly_reminder_authority_owner(self) -> WeeklyReminderAuthorityOwner: ...

    def create_grounded_weekly_owner_draft(
        self, request: WeeklyOwnerDraftRequest, model: WeeklyExplanationModel
    ) -> WeeklyOwnerDraftResult: ...

    def weekly_owner_key(self) -> tuple[str, str, str]: ...

    def weekly_owner_storage_authority(self) -> WeeklyOwnerStorageAuthority: ...


@dataclass(frozen=True, slots=True)
class WeeklyOperationsDispatchIncident(Exception):
    customer_key: str
    reason: str

    @override
    def __str__(self) -> str:
        return f"weekly operations dispatch failed for {self.customer_key}: {self.reason}"


def _same_snapshot(
    host: WeeklyOperationsHost,
    coordinator: WeeklyOperationsCoordinator,
    owner: WeeklyReminderAuthorityOwner,
    expected: WeeklyOperationsTickSnapshot,
    now: datetime,
) -> bool:
    try:
        if not coordinator.refresh_live_registry():
            return False
        if not host.weekly_operations_authority_current(expected):
            return False
        current = owner.tick_snapshot(expected.runtime.customer_key, now)
    except WeeklyOperationsError:
        return False
    return (
        current.config == expected.config
        and current.receipt == expected.receipt
        and current.runtime == expected.runtime
        and current.customer.runtime.registered_binding
        == expected.customer.runtime.registered_binding
    )


def _project_checkin(snapshot: WeeklyOperationsTickSnapshot, day: date) -> None:
    customer = snapshot.customer
    request = CorrelationRequest(
        CorrelationScope(
            customer.store.customer_identity_digest, day, CorrelationAction.CHECKIN
        ),
        customer.source,
    )
    _ = CanonicalCheckinCorrelationTransaction(customer.store, request).commit()


def _validate_sidecar(snapshot: WeeklyOperationsTickSnapshot) -> None:
    rows = snapshot.customer.store.read()
    with snapshot.customer.source.read_locked() as canonical:
        for row in rows:
            if row.canonical_sequence > len(canonical.sequence_rows):
                raise WeeklyOperationsError("canonical sidecar pin is stale")
            expected = canonical_prefix_pin(canonical, row.canonical_sequence)
            if (expected.sequence, expected.digest) != (
                row.canonical_sequence, row.canonical_digest
            ):
                raise WeeklyOperationsError("canonical sidecar digest disagrees")


def _completed_fields(snapshot: WeeklyOperationsTickSnapshot, row: WeeklyOperationRow) -> int:
    if row.source_event_id is None:
        return 0
    with snapshot.customer.source.read_locked() as canonical:
        event = next(
            (item for item in canonical.events if item.event_id == row.source_event_id),
            None,
        )
    checkin = None if event is None else event.check_in
    if checkin is None:
        return 0
    return min(len(checkin.model_dump(exclude_none=True)), 32)


def _current_bound_summary(
    host: WeeklyOperationsHost,
    coordinator: WeeklyOperationsCoordinator,
    owner: WeeklyReminderAuthorityOwner,
    expected: WeeklyOperationsTickSnapshot,
    starts_on: date,
    now: datetime,
) -> BoundWeeklySummaryForOwnerDraft:
    if not _same_snapshot(host, coordinator, owner, expected, now):
        raise WeeklyOperationsDispatchIncident(
            expected.runtime.customer_key, "authority drift"
        )
    current = owner.tick_snapshot(expected.runtime.customer_key, now)
    if current.runtime != expected.runtime:
        raise WeeklyOperationsDispatchIncident(expected.runtime.customer_key, "authority drift")
    summary = build_weekly_operations_summary(
        current.customer.source, current.customer.store, starts_on
    )
    return bind_weekly_summary_for_owner_draft(
        current.customer.source, current.customer.store, summary
    )


async def _dispatch_customer(
    host: WeeklyOperationsHost,
    coordinator: WeeklyOperationsCoordinator,
    owner: WeeklyReminderAuthorityOwner,
    snapshot: WeeklyOperationsTickSnapshot,
    tasks: Sequence[WeeklyOperationsTask],
    now: datetime,
) -> None:
    key = snapshot.runtime.customer_key
    _project_checkin(snapshot, now.date())
    _validate_sidecar(snapshot)
    for task in sorted(tasks, key=lambda item: item.kind == "cutoff"):
        failure = await send_weekly_operations_task(host, coordinator, task, local_now=now)
        if failure is not None:
            raise WeeklyOperationsDispatchIncident(key, failure)
    current = owner.tick_snapshot(key, now)
    rows = tuple(row for row in current.customer.store.read() if row.kst_day == now.date())
    if rows:
        row = rows[-1]
        card = Topic59DayCard.from_sidecar(
            row,
            completed_field_count=_completed_fields(current, row),
            draft_state=Topic59DraftState.NOT_CREATED,
            approval_state=Topic59ApprovalState.NOT_REQUESTED,
            delivery_state=Topic59DeliveryState.NOT_SENT,
            candidate_digest=current.runtime.candidate_digest,
        )
        is_current = lambda: _same_snapshot(
            host, coordinator, owner, current, now
        )
        result = await Topic59DayCardProjector(
            coordinator.profile_root / "data" / "weekly-operations-topic59.jsonl"
        ).publish(
            Topic59PublicationRequest(card, current.config, current.receipt, current.runtime),
            RevalidatingTopic59Transport(host, is_current),
        )
        if result.disposition in {
            Topic59PublicationDisposition.REFUSED,
            Topic59PublicationDisposition.INCIDENT,
        }:
            raise WeeklyOperationsDispatchIncident(key, str(result.reason))
    if now.weekday() != current.config.weekly_weekday:
        return
    starts_on = now.date() - timedelta(days=7)
    bound = _current_bound_summary(
        host, coordinator, owner, current, starts_on, now
    )
    request = WeeklyOwnerDraftRequest(
        key,
        coordinator.weekly_owner_key(),
        current.config,
        current.receipt,
        current.runtime,
        bound,
        coordinator.weekly_owner_storage_authority().binding_digest,
        lambda: _current_bound_summary(
            host, coordinator, owner, current, starts_on, now
        ),
    )
    result = coordinator.create_grounded_weekly_owner_draft(
        request,
        RevalidatingWeeklyModel(
            host, lambda: _same_snapshot(host, coordinator, owner, current, now)
        ),
    )
    if not result.accepted:
        raise WeeklyOperationsDispatchIncident(key, result.error or "owner draft rejected")


async def dispatch_weekly_operations_tick(
    host: WeeklyOperationsHost,
    coordinator: WeeklyOperationsCoordinator,
    tasks: Sequence[WeeklyOperationsTask],
    *,
    local_now: datetime,
) -> tuple[str, ...]:
    """Run enabled customers in snapshot/reminder/card/Monday order."""
    owner = coordinator.weekly_reminder_authority_owner
    failures: list[str] = []
    for key in owner.customer_keys:
        try:
            snapshot = owner.tick_snapshot(key, local_now)
            due = tuple(task for task in tasks if task.customer_key == key and task.kind in {"reminder", "cutoff"})
            await _dispatch_customer(host, coordinator, owner, snapshot, due, local_now)
        except (OSError, WeeklyOperationsError, WeeklyOperationsDispatchIncident) as error:
            failures.append(str(error))
    return tuple(failures)
