from __future__ import annotations

import json
from datetime import UTC, datetime
from uuid import uuid4

import pytest
from fastapi.testclient import TestClient

from app.modules.sync import repository as repo
from app.modules.sync.engine import SyncEngine
from tests.orchestrator_dual_harness import OrchestratorDualHubHarness, make_enterprise


@pytest.fixture()
def chaos_harness(client: TestClient):
    enterprise_id = make_enterprise(client)
    harness = OrchestratorDualHubHarness(client, enterprise_id)
    hub_a, hub_b = harness.pair_hubs()
    return harness, hub_a, hub_b


def test_duplicate_ack_safe(client: TestClient, chaos_harness):
    harness, hub_a, _hub_b = chaos_harness
    record_id = "chaos-ack-1"
    event = harness.make_component_event(
        hub_a, record_id, name="DupAck", version=1, content_hash="da1", event_id="evt-dup-ack"
    )
    harness.push_from(hub_a, [event])
    events = harness.pull_for(hub_a)  # hub should not see own events
    assert events == []
    resp = client.post(
        "/api/v1/sync/changes/ack",
        headers=hub_a.auth_header(),
        json={"event_ids": ["evt-dup-ack"], "direction": "inbound"},
    )
    assert resp.status_code == 200


def test_out_of_order_seq_replay_idempotent(client: TestClient, chaos_harness):
    harness, hub_a, hub_b = chaos_harness
    record_id = "chaos-seq-1"
    e2 = harness.make_component_event(
        hub_a, record_id, name="Second", version=2, content_hash="s2", event_id="evt-seq-2"
    )
    e1 = harness.make_component_event(
        hub_a, record_id, name="First", version=1, content_hash="s1", event_id="evt-seq-1"
    )
    harness.push_from(hub_a, [e2])
    harness.push_from(hub_a, [e1])
    harness.drain()
    row = harness.get_catalog(hub_b, "component", record_id)
    assert row is not None
    assert row["content_hash"] in {"s1", "s2"}


def test_resolve_keep_orchestrator_fanout(client: TestClient, chaos_harness):
    harness, hub_a, hub_b = chaos_harness
    record_id = "chaos-resolve-1"
    harness.push_from(
        hub_a,
        [
            {
                "event_id": str(uuid4()),
                "seq": 1,
                "domain": "global",
                "table": "component",
                "record_id": record_id,
                "action": "upsert",
                "version": 1,
                "content_hash": "orch-hash",
                "payload": {
                    "name": "Orch",
                    "type": "grain",
                    "dry_matter": 50.0,
                    "id": record_id,
                    "version": 1,
                    "content_hash": "orch-hash",
                },
                "emitted_at": datetime.now(UTC).isoformat(),
                "origin_site_id": hub_a.hub_site_id,
            }
        ],
    )
    harness.push_from(
        hub_b,
        [
            {
                "event_id": str(uuid4()),
                "seq": 1,
                "domain": "global",
                "table": "component",
                "record_id": record_id,
                "action": "upsert",
                "version": 1,
                "content_hash": "hub-hash",
                "payload": {
                    "name": "HubB",
                    "type": "grain",
                    "dry_matter": 60.0,
                    "id": record_id,
                    "version": 1,
                    "content_hash": "hub-hash",
                },
                "emitted_at": datetime.now(UTC).isoformat(),
                "origin_site_id": hub_b.hub_site_id,
            }
        ],
    )
    conflicts = repo.list_conflicts(harness.enterprise_id, "pending")
    assert len(conflicts) == 1
    engine = SyncEngine(harness.enterprise_id, SyncEngine.ORCHESTRATOR_SITE_ID)
    engine.resolve_conflict(conflicts[0].id, "admin", "keep_orchestrator")
    harness.drain()
    row = harness.get_orchestrator_catalog("component", record_id)
    assert row is not None
    assert row["content_hash"] == "orch-hash"


def test_resolve_keep_hub_fanout(client: TestClient, chaos_harness):
    harness, hub_a, hub_b = chaos_harness
    record_id = "chaos-resolve-hub-1"
    harness.push_from(
        hub_a,
        [
            {
                "event_id": str(uuid4()),
                "seq": 1,
                "domain": "global",
                "table": "component",
                "record_id": record_id,
                "action": "upsert",
                "version": 1,
                "content_hash": "orch-hash2",
                "payload": {
                    "name": "Orch",
                    "type": "grain",
                    "dry_matter": 50.0,
                    "id": record_id,
                    "version": 1,
                    "content_hash": "orch-hash2",
                },
                "emitted_at": "2026-07-15T10:00:00Z",
                "origin_site_id": hub_a.hub_site_id,
            }
        ],
    )
    harness.push_from(
        hub_b,
        [
            {
                "event_id": str(uuid4()),
                "seq": 1,
                "domain": "global",
                "table": "component",
                "record_id": record_id,
                "action": "upsert",
                "version": 1,
                "content_hash": "hub-b-hash2",
                "payload": {
                    "name": "Hub B wins",
                    "type": "grain",
                    "dry_matter": 77.0,
                    "id": record_id,
                    "version": 1,
                    "content_hash": "hub-b-hash2",
                },
                "emitted_at": "2026-07-15T10:01:00Z",
                "origin_site_id": hub_b.hub_site_id,
            }
        ],
    )
    conflicts = repo.list_conflicts(harness.enterprise_id, "pending")
    assert len(conflicts) == 1
    engine = SyncEngine(harness.enterprise_id, SyncEngine.ORCHESTRATOR_SITE_ID)
    engine.resolve_conflict(conflicts[0].id, "admin", "keep_hub")
    harness.drain()
    row = harness.get_orchestrator_catalog("component", record_id)
    assert row is not None
    assert row["content_hash"] == "hub-b-hash2"
    assert row["name"] == "Hub B wins"
