from __future__ import annotations

from dataclasses import asdict, dataclass
import json
from pathlib import Path
from typing import Any, Iterable

from src.ctb_copy.collectors.hyperliquid_vault_collector import HyperliquidVaultCollector
from src.ctb_copy.collectors.hyperliquid_wallet_collector import HyperliquidWalletCollector
from src.ctb_copy.collectors.market_context_collector import MarketContextCollector
from src.ctb_copy.discovery_registry import DiscoveryRegistry
from src.ctb_copy.read_only_guard import DEFAULT_READ_ONLY_GUARD, ReadOnlyGuard
from src.ctb_copy.runtime_paths import CopyResearchPaths, day_string, ensure_runtime_dirs, utc_now_iso
from src.ctb_copy.watchlist import enabled_vaults, enabled_wallets, load_watchlist, validate_watchlist, watchlist_summary


@dataclass(frozen=True)
class CollectorRunSummary:
    runtime_dir: Path
    day: str
    status: str
    vault_summaries_count: int
    vault_watchlist_count: int
    vault_snapshot_count: int
    wallet_watchlist_count: int
    wallet_snapshots: int
    market_context_snapshots: int
    vault_collection_status: str
    api_response_shape_known: bool
    new_leaders: tuple[str, ...]
    disappeared_leaders: tuple[str, ...]
    data_quality: tuple[str, ...]
    partial_errors: tuple[str, ...]
    snapshot_paths: dict[str, str]
    watchlist: dict[str, Any]
    guard: ReadOnlyGuard = DEFAULT_READ_ONLY_GUARD

    @property
    def read_only(self) -> bool:
        return self.guard.read_only

    @property
    def live_orders(self) -> bool:
        return self.guard.live_orders

    @property
    def vault_deposits(self) -> bool:
        return self.guard.vault_deposits

    @property
    def wallet_execution(self) -> bool:
        return self.guard.wallet_execution

    @property
    def signing_enabled(self) -> bool:
        return self.guard.signing_enabled

    @property
    def vault_snapshots(self) -> int:
        return self.vault_snapshot_count


def _vault_leader_id(raw: dict[str, object], fallback: str) -> str:
    return str(raw.get("vaultAddress") or raw.get("address") or raw.get("name") or fallback)


def _collector_summary_payload(summary: CollectorRunSummary) -> dict[str, Any]:
    payload = asdict(summary)
    payload["runtime_dir"] = str(summary.runtime_dir)
    payload["guard"] = summary.guard.as_dict()
    return payload


def write_collector_result(summary: CollectorRunSummary, paths: CopyResearchPaths) -> Path:
    target = paths.collector_result_file(summary.day)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(json.dumps(_collector_summary_payload(summary), indent=2, sort_keys=True) + "\n", encoding="utf-8")
    return target


def run_copy_research_collection(
    *,
    runtime_dir: str | Path | None = None,
    wallet_addresses: Iterable[str] = (),
    vault_addresses: Iterable[str] = (),
    env: str = "mainnet",
) -> CollectorRunSummary:
    guard = DEFAULT_READ_ONLY_GUARD
    guard.assert_safe()
    paths = CopyResearchPaths(Path(runtime_dir) if runtime_dir else CopyResearchPaths().root)
    day = day_string()
    ensure_runtime_dirs(paths, day)
    now = utc_now_iso()
    registry = DiscoveryRegistry.load(paths.discovery_registry_file)
    new_leaders: list[str] = []
    partial_errors: list[str] = []
    data_quality: list[str] = []
    observed_leaders: set[str] = set()

    watchlist_file_exists = paths.watchlist_file.exists()
    watch_payload = load_watchlist(paths.watchlist_file)
    validation = validate_watchlist(watch_payload)
    if not validation.ok:
        data_quality.extend(f"watchlist_invalid:{error}" for error in validation.errors)
        watch_wallets = ()
        watch_vaults = ()
    else:
        watch_wallets = enabled_wallets(watch_payload)
        watch_vaults = enabled_vaults(watch_payload)

    explicit_wallets = tuple(wallet_addresses)
    explicit_vaults = tuple(vault_addresses)
    wallet_targets = tuple(dict.fromkeys([entry.address for entry in watch_wallets] + list(explicit_wallets)))
    vault_targets = tuple(dict.fromkeys([entry.vault_address for entry in watch_vaults] + list(explicit_vaults)))

    vault_summaries = []
    vault_details = []
    wallets = []
    market_context = []
    vault_collection_status = "not_started"
    api_response_shape_known = False

    vault_collector = HyperliquidVaultCollector(env=env)
    try:
        vault_summaries = vault_collector.collect_vault_summaries()
        api_response_shape_known = True
        vault_collection_status = "ok"
        vault_collector.write_jsonl(vault_summaries, paths.snapshot_file("vaults", day))
        for snapshot in vault_summaries:
            leader_id = _vault_leader_id(snapshot.raw, snapshot.vault_id)
            observed_leaders.add(f"vault:{leader_id}")
            _record, created = registry.upsert_seen(leader_id, "vault", snapshot.source, seen_at=now)
            if created:
                new_leaders.append(f"vault:{leader_id}")
    except Exception as exc:  # pragma: no cover - integration/API degradation path
        vault_collection_status = "degraded"
        api_response_shape_known = False
        partial_errors.append(f"vault_summaries:{type(exc).__name__}")

    for vault_address in vault_targets:
        try:
            snapshot = vault_collector.collect_vault_detail(vault_address)
            vault_details.append(snapshot)
            observed_leaders.add(f"vault:{vault_address}")
            _record, created = registry.upsert_seen(vault_address, "vault", snapshot.source, seen_at=now, status="watchlist")
            if created:
                new_leaders.append(f"vault:{vault_address}")
        except Exception as exc:  # pragma: no cover - integration/API degradation path
            partial_errors.append(f"vault_detail:{type(exc).__name__}:{vault_address[:6]}")
    if vault_details:
        vault_collector.write_jsonl(vault_details, paths.snapshot_file("vault_details", day))

    wallet_collector = HyperliquidWalletCollector(env=env)
    for address in wallet_targets:
        try:
            snapshot = wallet_collector.collect_wallet_snapshot(address)
            wallets.append(snapshot)
            observed_leaders.add(f"wallet:{address}")
            _record, created = registry.upsert_seen(address, "wallet", snapshot.source, seen_at=now, status="watchlist")
            if created:
                new_leaders.append(f"wallet:{address}")
        except Exception as exc:  # pragma: no cover - integration/API degradation path
            partial_errors.append(f"wallet:{type(exc).__name__}:{address[:6]}")
    if wallets:
        wallet_collector.write_jsonl(wallets, paths.snapshot_file("wallets", day))

    try:
        context_collector = MarketContextCollector(env=env)
        market_context_snapshot = context_collector.collect()
        market_context = [market_context_snapshot]
        context_collector.write_jsonl(market_context, paths.snapshot_file("market_context", day))
        if market_context_snapshot.summary.get("api_status") != "ok":
            partial_errors.extend(f"market_context:{err}" for err in market_context_snapshot.summary.get("api_errors", []))
    except Exception as exc:  # pragma: no cover
        partial_errors.append(f"market_context:{type(exc).__name__}")

    disappeared = tuple(sorted(key for key in registry.records if key not in observed_leaders and registry.records[key].last_seen_at < now))
    if partial_errors:
        data_quality.extend(partial_errors)
    if not data_quality:
        data_quality.append("ok")
    status = "ok" if data_quality == ["ok"] else "degraded"

    registry.save()
    summary = CollectorRunSummary(
        runtime_dir=paths.root,
        day=day,
        status=status,
        vault_summaries_count=len(vault_summaries),
        vault_watchlist_count=len(vault_targets),
        vault_snapshot_count=len(vault_summaries) + len(vault_details),
        wallet_watchlist_count=len(wallet_targets),
        wallet_snapshots=len(wallets),
        market_context_snapshots=len(market_context),
        vault_collection_status=vault_collection_status,
        api_response_shape_known=api_response_shape_known,
        new_leaders=tuple(new_leaders),
        disappeared_leaders=disappeared,
        data_quality=tuple(data_quality),
        partial_errors=tuple(partial_errors),
        snapshot_paths={
            "vaults": str(paths.snapshot_file("vaults", day)),
            "vault_details": str(paths.snapshot_file("vault_details", day)),
            "wallets": str(paths.snapshot_file("wallets", day)),
            "market_context": str(paths.snapshot_file("market_context", day)),
            "collector_result": str(paths.collector_result_file(day)),
        },
        watchlist=watchlist_summary(watch_payload, exists=watchlist_file_exists),
        guard=guard,
    )
    write_collector_result(summary, paths)
    return summary
