from __future__ import annotations

import json

import pytest

from src.ctb_copy.discovery.candidate_discovery import (
    candidates_to_watchlist_entries,
    dedupe_candidates,
    make_observation_candidate,
    validate_candidate_address,
    write_discovery_report,
)

DUMMY_A = "0x" + "a" * 40
DUMMY_B = "0x" + "b" * 40


def test_candidate_validates_address_and_defaults_to_observation_only() -> None:
    candidate = make_observation_candidate(
        address=DUMMY_A,
        label="dummy",
        source="unit_fixture",
        source_url="https://example.invalid/leaderboard",
        reason="dummy public leaderboard fixture",
        discovered_at="2026-06-14T00:00:00+00:00",
    )

    assert validate_candidate_address(DUMMY_A) is True
    assert candidate.enabled_default is False
    assert candidate.risk_note == "observation_candidate_only; third_party_source_not_ground_truth"
    entry = candidate.to_watchlist_entry(enabled=True, label="public_observation_1")
    assert entry == {
        "address": DUMMY_A,
        "label": "public_observation_1",
        "source": "manual_discovery",
        "enabled": True,
        "notes": "read-only observation only; no copy permission",
    }


def test_invalid_address_is_rejected() -> None:
    assert validate_candidate_address("not-an-address") is False
    with pytest.raises(ValueError):
        make_observation_candidate(address="not-an-address", label="bad", source="fixture", reason="invalid")


def test_dedupe_preserves_order_and_watchlist_entries_limit() -> None:
    first = make_observation_candidate(address=DUMMY_A, label="a", source="fixture", reason="first")
    duplicate = make_observation_candidate(address=DUMMY_A.upper().replace("X", "x", 1), label="dup", source="fixture", reason="dupe")
    second = make_observation_candidate(address=DUMMY_B, label="b", source="fixture", reason="second")

    deduped = dedupe_candidates([first, duplicate, second])
    entries = candidates_to_watchlist_entries(deduped, limit=1, enabled=True)

    assert [candidate.address for candidate in deduped] == [DUMMY_A, DUMMY_B]
    assert len(entries) == 1
    assert entries[0]["label"] == "public_observation_1"
    assert entries[0]["enabled"] is True


def test_discovery_report_writes_structured_payload_outside_repo(tmp_path) -> None:
    candidate = make_observation_candidate(address=DUMMY_A, label="a", source="fixture", reason="first")

    target = write_discovery_report([candidate], tmp_path / "runtime" / "candidate_discovery.json")

    payload = json.loads(target.read_text())
    assert payload["candidate_count"] == 1
    assert payload["candidates"][0]["address"] == DUMMY_A
    assert "Hyperliquid Info API" in payload["ground_truth_note"]
