from __future__ import annotations

import os
from dataclasses import replace
from datetime import date
from pathlib import Path


from checkin_cli.models import EventType
from checkin_cli.weekly_operations import CustomerKey
from checkin_cli.weekly_operations_correlation import (
    CanonicalCheckinCorrelationTransaction,
    CorrelationAction,
    CorrelationRequest,
    CorrelationScope,
)
from checkin_cli.weekly_operations_customer_authority import CanonicalCheckinCustomerAuthority
from checkin_cli.weekly_operations_customer_authority_factory import (
    open_canonical_checkin_customer_authority,
)
from tests._weekly_operations_correlation_support import (
    CUSTOMER,
    DAY,
    event,
    expect_conflict,
    registered_source_at,
    store_at,
)


def _request(source: CanonicalCheckinCustomerAuthority) -> CorrelationRequest:
    return CorrelationRequest(
        CorrelationScope(
            source.customer_identity_digest,
            date.fromisoformat(DAY),
            CorrelationAction.CHECKIN,
        ),
        source,
    )


def test_cross_customer_correction_tree_cannot_mutate_sidecar(tmp_path: Path) -> None:
    root = event("authority_other_root_001")
    correction = event(
        "authority_other_correct1", EventType.CORRECTION, supersedes=root.event_id
    )
    fixture = store_at(tmp_path / "sidecar")
    other = registered_source_at(
        tmp_path / "other",
        CustomerKey("other_customer_001"),
        root,
        correction,
        registry_authority=fixture.authority,
    )

    with expect_conflict("customer authority disagree"):
        _ = CanonicalCheckinCorrelationTransaction(
            fixture.store, _request(other.source)
        ).commit()
    assert fixture.store.read() == ()
    other.close()
    fixture.close()


def test_same_registered_binding_reopens_with_exact_digest(tmp_path: Path) -> None:
    fixture = store_at(tmp_path / "authority")
    canonical = registered_source_at(
        tmp_path / "profile",
        CUSTOMER,
        event("authority_restart_root1"),
        registry_authority=fixture.authority,
    )
    digest = canonical.source.binding_digest
    canonical.source.close()

    reopened = open_canonical_checkin_customer_authority(
        canonical.runtime, canonical.source.registered_binding, fixture.authority
    )

    assert reopened.binding_digest == digest
    reopened.close()
    fixture.close()


def test_altered_binding_is_rejected_at_construction(tmp_path: Path) -> None:
    fixture = store_at(tmp_path / "authority")
    canonical = registered_source_at(
        tmp_path / "profile",
        CUSTOMER,
        event("authority_altered_root1"),
        registry_authority=fixture.authority,
    )

    with expect_conflict("binding digest drift"):
        _ = replace(canonical.source.binding, binding_digest="f" * 64)
    canonical.close()
    fixture.close()


def _assert_substitution_fails(tmp_path: Path, attack: str) -> None:
    fixture = store_at(tmp_path / "sidecar")
    canonical = registered_source_at(
        tmp_path / "profile",
        CUSTOMER,
        event(f"authority_{attack}_root_01"),
        registry_authority=fixture.authority,
    )
    events_path = canonical.transaction.events_path
    if attack == "replace":
        original = events_path.with_suffix(".original")
        _ = events_path.rename(original)
        _ = events_path.write_bytes(original.read_bytes())
        events_path.chmod(0o600)
    elif attack == "hardlink":
        _ = os.link(events_path, events_path.with_suffix(".hardlink"))
    else:
        canonical.transaction.events_path = events_path.with_name("other.jsonl")

    with expect_conflict():
        _ = CanonicalCheckinCorrelationTransaction(
            fixture.store, _request(canonical.source)
        ).commit()
    assert fixture.store.read() == ()
    canonical.close()
    fixture.close()


def test_file_replacement_fails_before_mutation(tmp_path: Path) -> None:
    _assert_substitution_fails(tmp_path, "replace")


def test_hardlink_fails_before_mutation(tmp_path: Path) -> None:
    _assert_substitution_fails(tmp_path, "hardlink")


def test_transaction_path_fails_before_mutation(tmp_path: Path) -> None:
    _assert_substitution_fails(tmp_path, "transaction")


def test_customer_root_symlink_replacement_fails_before_mutation(tmp_path: Path) -> None:
    fixture = store_at(tmp_path / "sidecar")
    canonical = registered_source_at(
        tmp_path / "profile",
        CUSTOMER,
        event("authority_root_replace1"),
        registry_authority=fixture.authority,
    )
    customer_root = canonical.runtime.customer_root
    moved = customer_root.with_name("moved-customer-root")
    _ = customer_root.rename(moved)
    _ = customer_root.symlink_to(moved, target_is_directory=True)

    with expect_conflict("symlink substitution"):
        _ = CanonicalCheckinCorrelationTransaction(
            fixture.store, _request(canonical.source)
        ).commit()
    assert fixture.store.read() == ()
    canonical.close()
    fixture.close()
