"""Descriptor-bound append-only canonical-authority registration store."""

from __future__ import annotations

import fcntl
import hashlib
import json
import os
import stat
from collections.abc import Generator
from contextlib import contextmanager
from datetime import datetime, timezone
from typing import Final, final

from checkin_cli.weekly_operations import (
    CanonicalAuthorityAlreadyRegistered,
    CustomerIdentityDigest,
    WeeklyOperationsConflict,
    WeeklyOperationsCorruption,
    ZERO_DIGEST,
)
from checkin_cli.weekly_operations_authority import WeeklyOperationsAuthorityRoot
from checkin_cli.weekly_operations_canonical_registry_history import (
    REGISTRATION_SCHEMA,
    REGISTRY_NAME,
    CanonicalAuthorityRegistrationRow,
    canonical_registration_row,
    registration_row_digest,
    validate_registration_history,
)
from checkin_cli.weekly_operations_registered_binding import (
    RegisteredCanonicalCheckinCustomerBinding,
    create_registered_binding,
)
from checkin_cli.weekly_operations_canonical_snapshot import (
    CanonicalCheckinCustomerBinding,
    CanonicalFilePin,
)

_FLAGS: Final = os.O_RDWR | os.O_APPEND | os.O_NONBLOCK | os.O_NOFOLLOW | os.O_CLOEXEC


def _verify_file(root: int, descriptor: int) -> None:
    opened = os.fstat(descriptor)
    named = os.stat(REGISTRY_NAME, dir_fd=root, follow_symlinks=False)
    identity = opened.st_dev, opened.st_ino
    safe = (
        stat.S_ISREG(opened.st_mode)
        and opened.st_uid == os.geteuid()
        and opened.st_nlink == 1
        and stat.S_IMODE(opened.st_mode) == 0o600
        and identity == (named.st_dev, named.st_ino)
    )
    if not safe:
        raise WeeklyOperationsCorruption("canonical authority registry inode is unsafe")


def _read(descriptor: int) -> bytes:
    size = os.fstat(descriptor).st_size
    first = os.pread(descriptor, size, 0)
    second = os.pread(descriptor, size, 0)
    if first != second or len(first) != size:
        raise WeeklyOperationsCorruption("canonical authority registry changed during read")
    return first


def _open(authority: WeeklyOperationsAuthorityRoot) -> int:
    try:
        return os.open(REGISTRY_NAME, _FLAGS, dir_fd=authority.descriptor)
    except FileNotFoundError as error:
        raise WeeklyOperationsConflict("canonical authority registry is missing") from error
    except OSError as error:
        raise WeeklyOperationsCorruption("canonical authority registry open failed") from error


@contextmanager
def locked_registration_history(
    authority: WeeklyOperationsAuthorityRoot, *, exclusive: bool
) -> Generator[tuple[int, tuple[CanonicalAuthorityRegistrationRow, ...]]]:
    authority.verify()
    descriptor = _open(authority)
    try:
        fcntl.flock(descriptor, fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)
        _verify_file(authority.descriptor, descriptor)
        rows = validate_registration_history(_read(descriptor))
        authority.verify()
        yield descriptor, rows
        _verify_file(authority.descriptor, descriptor)
        _ = validate_registration_history(_read(descriptor))
        authority.verify()
    finally:
        fcntl.flock(descriptor, fcntl.LOCK_UN)
        os.close(descriptor)


def _pin_fields(prefix: str, pin: CanonicalFilePin) -> dict[str, int]:
    return {
        f"{prefix}_device": pin.device,
        f"{prefix}_inode": pin.inode,
        f"{prefix}_mode": pin.mode,
        f"{prefix}_owner": pin.owner,
        f"{prefix}_links": pin.links,
    }


def _registry_digest(authority: WeeklyOperationsAuthorityRoot) -> str:
    binding = authority.binding
    value = [binding.registry_device, binding.registry_inode, binding.registry_mode, binding.registry_owner, binding.registry_links, binding.registry_digest]
    return hashlib.sha256(json.dumps(value, separators=(",", ":")).encode()).hexdigest()


def _candidate(
    authority: WeeklyOperationsAuthorityRoot,
    binding: CanonicalCheckinCustomerBinding,
    rows: tuple[CanonicalAuthorityRegistrationRow, ...],
) -> CanonicalAuthorityRegistrationRow:
    values: dict[str, str | int | datetime] = {
        "schema_version": REGISTRATION_SCHEMA,
        "customer_identity_digest": binding.customer_identity_digest,
        "binding_digest": binding.binding_digest,
        "authority_binding_digest": authority.binding.binding_digest,
        "authority_marker_digest": authority.binding.marker_digest,
        "registry_device": authority.binding.registry_device,
        "registry_inode": authority.binding.registry_inode,
        "registry_binding_digest": _registry_digest(authority),
        **_pin_fields("root", binding.pins.root),
        **_pin_fields("events", binding.pins.events),
        **_pin_fields("sequence", binding.pins.sequence),
        **_pin_fields("lock", binding.pins.lock),
        "registration_epoch": len(rows) + 1,
        "occurred_at_utc": datetime.now(timezone.utc),
        "predecessor_row_digest": rows[-1].row_digest if rows else ZERO_DIGEST,
        "row_digest": ZERO_DIGEST,
    }
    provisional = CanonicalAuthorityRegistrationRow.model_validate(values)
    return provisional.model_copy(
        update={"row_digest": registration_row_digest(provisional)}
    )


@final
class CanonicalAuthorityRegistrationSlot:
    """One locked customer registration opportunity."""

    def __init__(
        self,
        authority: WeeklyOperationsAuthorityRoot,
        descriptor: int,
        rows: tuple[CanonicalAuthorityRegistrationRow, ...],
        customer: CustomerIdentityDigest,
    ) -> None:
        self._authority = authority
        self._descriptor = descriptor
        self._rows = rows
        self._customer = customer

    def append(
        self, binding: CanonicalCheckinCustomerBinding
    ) -> RegisteredCanonicalCheckinCustomerBinding:
        if binding.customer_identity_digest != self._customer:
            raise WeeklyOperationsConflict("canonical registration customer disagrees")
        row = _candidate(self._authority, binding, self._rows)
        payload = canonical_registration_row(row) + b"\n"
        try:
            offset = 0
            while offset < len(payload):
                offset += os.write(self._descriptor, payload[offset:])
            os.fsync(self._descriptor)
            os.fsync(self._authority.descriptor)
        except OSError as error:
            raise WeeklyOperationsCorruption(
                "canonical authority registry append failed"
            ) from error
        expected = (*self._rows, row)
        if validate_registration_history(_read(self._descriptor)) != expected:
            raise WeeklyOperationsCorruption(
                "canonical authority registry append disagrees"
            )
        return create_registered_binding(
            binding, row.row_digest, self._authority.binding.binding_digest,
            self._authority.binding.marker_digest, _registry_digest(self._authority),
        )


@contextmanager
def one_time_registration(
    authority: WeeklyOperationsAuthorityRoot, customer: CustomerIdentityDigest
) -> Generator[CanonicalAuthorityRegistrationSlot]:
    with locked_registration_history(authority, exclusive=True) as locked:
        descriptor, rows = locked
        if any(row.customer_identity_digest == customer for row in rows):
            raise CanonicalAuthorityAlreadyRegistered("already_registered")
        yield CanonicalAuthorityRegistrationSlot(
            authority, descriptor, rows, customer
        )


def _matches(
    row: CanonicalAuthorityRegistrationRow,
    binding: RegisteredCanonicalCheckinCustomerBinding,
    authority: WeeklyOperationsAuthorityRoot,
) -> bool:
    canonical = binding.canonical_binding
    pins = canonical.pins
    expected = {
        "root": pins.root,
        "events": pins.events,
        "sequence": pins.sequence,
        "lock": pins.lock,
    }
    return (
        row.row_digest == binding.registration_row_digest
        and row.binding_digest == canonical.binding_digest
        and row.authority_binding_digest == binding.authority_binding_digest == authority.binding.binding_digest
        and row.authority_marker_digest == binding.authority_marker_digest == authority.binding.marker_digest
        and row.registry_binding_digest == binding.registry_binding_digest == _registry_digest(authority)
        and (row.registry_device, row.registry_inode) == (authority.binding.registry_device, authority.binding.registry_inode)
        and all(
            (
                getattr(row, f"{name}_device"),
                getattr(row, f"{name}_inode"),
                getattr(row, f"{name}_mode"),
                getattr(row, f"{name}_owner"),
                getattr(row, f"{name}_links"),
            )
            == (pin.device, pin.inode, pin.mode, pin.owner, pin.links)
            for name, pin in expected.items()
        )
    )


@contextmanager
def require_registered_binding(
    authority: WeeklyOperationsAuthorityRoot,
    customer: CustomerIdentityDigest,
    binding: RegisteredCanonicalCheckinCustomerBinding,
) -> Generator[None]:
    with locked_registration_history(authority, exclusive=False) as locked:
        _descriptor, rows = locked
        matches = tuple(row for row in rows if row.customer_identity_digest == customer)
        if len(matches) != 1 or not _matches(matches[0], binding, authority):
            raise WeeklyOperationsConflict("canonical authority registry binding disagrees")
        yield
