"""Orchestrator + two virtual hub clients for dual-hub sync E2E tests."""

from __future__ import annotations

import copy
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any
from uuid import uuid4

from fastapi.testclient import TestClient

from app.modules.sync import repository as repo


@dataclass
class HubClient:
    hub_site_id: str
    farm_hub_id: str
    api_key: str
    pull_cursor: int = 0
    local_catalog: dict[str, dict[str, dict[str, Any]]] = field(default_factory=dict)

    def auth_header(self) -> dict[str, str]:
        return {"Authorization": f"Hub {self.hub_site_id}:{self.api_key}"}


class OrchestratorDualHubHarness:
    def __init__(self, client: TestClient, enterprise_id: str) -> None:
        self.client = client
        self.enterprise_id = enterprise_id
        self.hub_a: HubClient | None = None
        self.hub_b: HubClient | None = None

    def pair_hubs(self) -> tuple[HubClient, HubClient]:
        suffix = uuid4().hex[:8]
        code_a = self._start_pairing()
        hub_a = self._confirm_hub(code_a, f"hub-site-a-{suffix}", "Hub A")
        code_b = self._start_pairing()
        hub_b = self._confirm_hub(code_b, f"hub-site-b-{suffix}", "Hub B")
        self.hub_a = hub_a
        self.hub_b = hub_b
        return hub_a, hub_b

    def _start_pairing(self) -> str:
        login = self.client.post(
            "/api/v1/auth/login",
            json={"email": "admin@compton.example", "password": "Admin1234"},
        )
        token = login.json()["access_token"]
        start = self.client.post(
            "/api/v1/enterprise/pair/start",
            headers={"Authorization": f"Bearer {token}"},
            json={"enterprise_id": self.enterprise_id, "farm_name": "Farm"},
        )
        return start.json()["code"]

    def _confirm_hub(self, code: str, hub_site_id: str, hub_name: str) -> HubClient:
        confirm = self.client.post(
            "/api/v1/enterprise/pair/confirm",
            json={"code": code, "hub_site_id": hub_site_id, "hub_name": hub_name},
        )
        body = confirm.json()
        return HubClient(
            hub_site_id=body["hub_site_id"],
            farm_hub_id=body["farm_hub_id"],
            api_key=body["api_key"],
        )

    def edit_local(self, hub: HubClient, table: str, record_id: str, payload: dict, *, version: int, content_hash: str) -> dict:
        row = copy.deepcopy(payload)
        row["id"] = record_id
        row["version"] = version
        row["content_hash"] = content_hash
        hub.local_catalog.setdefault(table, {})[record_id] = row
        return row

    def push_from(self, hub: HubClient, events: list[dict]) -> dict:
        resp = self.client.post(
            "/api/v1/sync/changes/push",
            headers=hub.auth_header(),
            json={"events": events},
        )
        resp.raise_for_status()
        return resp.json()

    def pull_for(self, hub: HubClient) -> list[dict]:
        resp = self.client.post(
            "/api/v1/sync/changes/pull",
            headers=hub.auth_header(),
            json={"cursor": hub.pull_cursor, "limit": 100},
        )
        resp.raise_for_status()
        body = resp.json()
        events = body.get("events") or []
        ack_ids: list[str] = []
        for ev in events:
            self._apply_local(hub, ev)
            ack_ids.append(ev["event_id"])
        if ack_ids:
            self.client.post(
                "/api/v1/sync/changes/ack",
                headers=hub.auth_header(),
                json={"event_ids": ack_ids, "direction": "inbound"},
            )
        if body.get("next_cursor") is not None:
            hub.pull_cursor = int(body["next_cursor"])
        return events

    def _apply_local(self, hub: HubClient, event: dict) -> None:
        table = event["table"]
        record_id = event["record_id"]
        hub.local_catalog.setdefault(table, {})[record_id] = {
            **(event.get("payload") or {}),
            "id": record_id,
            "version": event.get("version"),
            "content_hash": event.get("content_hash"),
        }

    def drain(self, hubs: list[HubClient] | None = None, rounds: int = 10) -> None:
        targets = hubs or [h for h in (self.hub_a, self.hub_b) if h]
        for _ in range(rounds):
            for hub in targets:
                self.pull_for(hub)

    def get_catalog(self, hub: HubClient, table: str, record_id: str) -> dict | None:
        return hub.local_catalog.get(table, {}).get(record_id)

    def get_orchestrator_catalog(self, table: str, record_id: str) -> dict | None:
        from app.modules.zootech.catalog_apply import load_catalog_row

        return load_catalog_row(self.enterprise_id, table, record_id)

    def make_component_event(
        self,
        hub: HubClient,
        record_id: str,
        *,
        name: str,
        version: int,
        content_hash: str,
        event_id: str | None = None,
        dry_matter: float = 88.0,
    ) -> dict:
        payload = {
            "name": name,
            "type": "grain",
            "dry_matter": dry_matter,
            "protein": 8.0,
            "energy": 1.2,
            "price": 0.0,
            "is_active": True,
        }
        self.edit_local(hub, "component", record_id, payload, version=version, content_hash=content_hash)
        return {
            "event_id": event_id or str(uuid4()),
            "seq": version,
            "domain": "global",
            "table": "component",
            "record_id": record_id,
            "action": "upsert",
            "version": version,
            "content_hash": content_hash,
            "payload": {**payload, "id": record_id, "version": version, "content_hash": content_hash},
            "emitted_at": datetime.now(UTC).isoformat(),
            "origin_site_id": hub.hub_site_id,
        }


def make_enterprise(client: TestClient) -> str:
    slug = f"test-ent-{uuid4().hex[:8]}"
    ent = repo.create_enterprise("Test Enterprise", slug)
    from app.modules.users.repository import get_user_by_email

    user = get_user_by_email("admin@compton.example")
    assert user
    repo.add_member(user.id, ent.id, "admin")
    return ent.id
