"""Todo 5 r3 descriptor-root and immutable-route regressions."""

from __future__ import annotations

import copy
import json
import os
from dataclasses import asdict, fields, replace
from datetime import datetime
from pathlib import Path

import anyio
import pytest

from checkin_cli.weekly_operations import WeeklyOperationsConflict
from checkin_cli.weekly_operations_lifecycle import ReminderDependencies, run_due_reminder
from checkin_cli.weekly_reminder_authority import (
    BoundWeeklyReminderCustomer,
    WeeklyReminderBindingInput,
)
from tests._weekly_operations_lifecycle_support import (
    FakeReminderProvider,
    KST,
    fixture_at,
)


def test_raw_root_and_route_are_absent_from_constructible_contracts(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    bound = fixture.request.bound_customer

    assert {item.name for item in fields(WeeklyReminderBindingInput)} == {
        "authorization", "ledger", "source", "store"
    }
    assert "profile_root" not in {item.name for item in fields(BoundWeeklyReminderCustomer)}
    assert "route_chat_id" not in {item.name for item in fields(BoundWeeklyReminderCustomer)}
    assert "route_topic_id" not in {item.name for item in fields(BoundWeeklyReminderCustomer)}
    serialized = json.dumps(asdict(bound.route), default=repr)
    assert "unauthorized-chat" not in serialized
    assert copy.copy(bound.route) == bound.route
    fixture.close()


def test_route_replace_copy_and_mutation_cannot_redirect(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    route = fixture.request.bound_customer.route

    with pytest.raises(TypeError):
        _ = replace(route, chat_id="unauthorized-chat")
    with pytest.raises((AttributeError, TypeError, WeeklyOperationsConflict)):
        setattr(route, "chat_id", "unauthorized-chat")
    assert route.chat_id == "chat"
    fixture.close()


def test_profile_root_inode_swap_fails_twice_before_provider(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    provider = FakeReminderProvider()
    root = tmp_path / "customer"
    moved = root.with_name(root.name + "-moved")
    os.rename(root, moved)
    root.mkdir(mode=0o700)

    for _ in range(2):
        with pytest.raises(WeeklyOperationsConflict, match="authority unavailable|binding drift|name drift|file unsafe"):
            _ = anyio.run(
                run_due_reminder,
                fixture.request,
                ReminderDependencies(provider, lambda: fixture.request.bound_customer),
            )
    assert provider.calls == 0
    os.rmdir(root)
    os.rename(moved, root)
    fixture.close()


def test_schedule_ledger_hardlink_is_rejected_at_acquisition(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 19, 59, 59, tzinfo=KST))
    root = tmp_path / "customer"
    ledger = root / "data" / "scheduled-deliveries.jsonl"
    alias = root / "data" / "ledger-alias"
    os.link(ledger, alias)

    with pytest.raises(WeeklyOperationsConflict, match="authority unavailable|binding drift|name drift|file unsafe"):
        fixture.request.bound_customer.ledger.verify()
    alias.unlink()
    fixture.close()

@pytest.mark.parametrize("replacement", ("regular", "symlink", "fifo"))
def test_named_ledger_replacement_is_typed_failure(
    tmp_path: Path, replacement: str
) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 19, 59, 59, tzinfo=KST))
    root = tmp_path / "customer"
    ledger = root / "data" / "scheduled-deliveries.jsonl"
    moved = ledger.with_suffix(".moved")
    _ = ledger.rename(moved)
    if replacement == "regular":
        _ = ledger.write_bytes(b"")
    elif replacement == "symlink":
        ledger.symlink_to(moved.name)
    else:
        os.mkfifo(ledger, mode=0o600)

    with pytest.raises(WeeklyOperationsConflict, match="authority unavailable|binding drift|name drift|file unsafe"):
        fixture.request.bound_customer.ledger.verify()
    ledger.unlink()
    _ = moved.rename(ledger)
    fixture.close()
