from __future__ import annotations

import json

from src.ctb_copy.collector_service import run_copy_research_collection
from src.ctb_copy.models import VaultSnapshot, WalletSnapshot
from src.ctb_copy.runtime_paths import CopyResearchPaths

DUMMY_WALLET = "0x" + "4" * 40
DUMMY_VAULT = "0x" + "5" * 40


class FakeVaultCollector:
    def __init__(self, env=None):
        self.env = env

    def collect_vault_summaries(self):
        return [VaultSnapshot(vault_id="summary-vault", observed_at_ms=1, source="test:vaultSummaries", raw={"vaultAddress": "summary-vault"})]

    def collect_vault_detail(self, vault_address):
        return VaultSnapshot(vault_id=vault_address, observed_at_ms=1, source="test:vaultDetails", raw={"vaultAddress": vault_address})

    def write_jsonl(self, snapshots, path):
        path.parent.mkdir(parents=True, exist_ok=True)
        with path.open("a", encoding="utf-8") as f:
            for snapshot in snapshots:
                f.write(json.dumps(snapshot.__dict__, sort_keys=True) + "\n")
        return path


class PartialWalletCollector:
    def __init__(self, env=None):
        self.env = env

    def collect_wallet_snapshot(self, wallet_address):
        raise ConnectionError("boom")

    def write_jsonl(self, snapshots, path):
        raise AssertionError("no wallet snapshots should be written on failure")


class FakeMarketContextCollector:
    def __init__(self, env=None):
        self.env = env

    def collect(self):
        class Snapshot:
            observed_at_ms = 1
            source = "test:market"
            raw = {"meta": {}}
            summary = {"api_status": "ok", "universe_count": 1}
        return Snapshot()

    def write_jsonl(self, snapshots, path):
        path.parent.mkdir(parents=True, exist_ok=True)
        with path.open("a", encoding="utf-8") as f:
            for snapshot in snapshots:
                f.write(json.dumps({"observed_at_ms": snapshot.observed_at_ms, "source": snapshot.source, "raw": snapshot.raw, "summary": snapshot.summary}) + "\n")
        return path


def test_collector_loads_watchlist_and_marks_partial_wallet_error_degraded(monkeypatch, tmp_path) -> None:
    paths = CopyResearchPaths(tmp_path / "copy_research")
    paths.watchlist_file.parent.mkdir(parents=True)
    paths.watchlist_file.write_text(json.dumps({
        "version": 1,
        "wallets": [{"address": DUMMY_WALLET, "label": "dummy", "source": "manual", "enabled": True, "notes": ""}],
        "vaults": [{"vault_address": DUMMY_VAULT, "label": "dummy-vault", "source": "manual", "enabled": True, "notes": ""}],
    }))
    monkeypatch.setattr("src.ctb_copy.collector_service.HyperliquidVaultCollector", FakeVaultCollector)
    monkeypatch.setattr("src.ctb_copy.collector_service.HyperliquidWalletCollector", PartialWalletCollector)
    monkeypatch.setattr("src.ctb_copy.collector_service.MarketContextCollector", FakeMarketContextCollector)

    summary = run_copy_research_collection(runtime_dir=paths.root)

    assert summary.status == "degraded"
    assert summary.wallet_watchlist_count == 1
    assert summary.wallet_snapshots == 0
    assert any(error.startswith("wallet:ConnectionError") for error in summary.partial_errors)
    assert summary.vault_watchlist_count == 1
    assert summary.vault_summaries_count == 1
    assert summary.vault_snapshot_count == 2
    assert summary.vault_collection_status == "ok"
    assert summary.api_response_shape_known is True
    assert summary.read_only is True
    assert summary.live_orders is False
    assert paths.collector_result_file(summary.day).exists()
    assert "Crypto_Agent" not in str(paths.collector_result_file(summary.day))


def test_collector_missing_empty_watchlist_is_ok(monkeypatch, tmp_path) -> None:
    monkeypatch.setattr("src.ctb_copy.collector_service.HyperliquidVaultCollector", FakeVaultCollector)
    monkeypatch.setattr("src.ctb_copy.collector_service.HyperliquidWalletCollector", PartialWalletCollector)
    monkeypatch.setattr("src.ctb_copy.collector_service.MarketContextCollector", FakeMarketContextCollector)

    summary = run_copy_research_collection(runtime_dir=tmp_path / "copy_research")

    assert summary.status == "ok"
    assert summary.watchlist["watchlist_exists"] is False
    assert summary.wallet_watchlist_count == 0
    assert summary.vault_watchlist_count == 0
    assert summary.wallet_snapshots == 0
