"""Run a complete custom JSON source under the public Client on either backend.

Run ``uv run python examples/online_json_source.py --backend asyncio`` or use
``--backend trio`` with the trio extra. A temporary complete input is created
locally; no public cache, authentication or production data is required.
"""

from __future__ import annotations

import argparse
import json
from collections.abc import Mapping
from datetime import UTC, datetime
from ipaddress import ip_network
from pathlib import Path
from tempfile import TemporaryDirectory

import anyio

from rpkiparrot import Client
from rpkiparrot.config import ClientConfig, FreshnessPolicy, JsonFileSourceConfig, SourceGroupConfig
from rpkiparrot.errors import InputError
from rpkiparrot.models import OriginStatus, ParsedDataset, PayloadKind, Vrp, VrpRecord
from rpkiparrot.query import export_snapshot
from rpkiparrot.readers import JsonReader, load_snapshot
from rpkiparrot.validation import validate_origin


class ApplicationFormat:
    """A small application-owned complete dataset with its own generation time."""

    format_id = "online-example-v1"

    def parse(self, document: Mapping[str, object]) -> ParsedDataset:
        """Preserve the input's original timestamp; never generate one while reading."""
        prefix, asn, generated = (document.get(key) for key in ("prefix", "origin", "generated"))
        if not isinstance(prefix, str) or type(asn) is not int or not isinstance(generated, str):
            raise InputError("example requires prefix, integer origin and original generation")
        try:
            network = ip_network(prefix, strict=True)
            generated_at = datetime.fromisoformat(generated)
        except ValueError as exc:
            raise InputError("invalid example prefix or generation") from exc
        return ParsedDataset(
            format_id=self.format_id,
            generated_at=generated_at,
            present=frozenset({PayloadKind.VRP}),
            vrps=(VrpRecord(vrp=Vrp(prefix=network, max_length=network.prefixlen, asn=asn)),),
        )


async def main() -> None:
    """Start an injected reader, wait for usable data, validate, and close all tasks."""
    with TemporaryDirectory() as directory:
        path = Path(directory) / "complete.json"
        raw = json.dumps(
            {"prefix": "192.0.2.0/24", "origin": 64496, "generated": datetime.now(UTC).isoformat()}
        )
        await anyio.to_thread.run_sync(path.write_text, raw)
        source = JsonFileSourceConfig(
            id="application",
            path=path,
            format=ApplicationFormat.format_id,
            reader_profile_id="application-schema-v1",
            freshness=FreshnessPolicy(max_age=60),
        )
        config = ClientConfig(
            groups=(
                SourceGroupConfig(id="primary", priority=0, sources=(source,)),
                SourceGroupConfig(id="standby", priority=1),
            )
        )
        reader = JsonReader([ApplicationFormat()])
        async with (
            Client(config, readers={source.id: reader}) as client,
            anyio.create_task_group() as tasks,
        ):
            await tasks.start(client.run)
            snapshot = await client.wait_ready(required=frozenset({PayloadKind.VRP}), timeout=5)
            result = validate_origin(snapshot, "192.0.2.0/24", 64496)
            assert result.status is OriginStatus.VALID
            restored = load_snapshot(export_snapshot(snapshot))
            assert restored.upstream_id == snapshot.id
            assert restored.active_groups[PayloadKind.VRP] == "primary"
            assert restored.sources[source.id].expires_at == snapshot.sources[source.id].expires_at
            assert validate_origin(restored, "192.0.2.0/24", 64496).status is OriginStatus.VALID
            print(f"Custom online source: {result.status.value}")
            await client.aclose()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--backend", choices=("asyncio", "trio"), default="asyncio")
    anyio.run(main, backend=parser.parse_args().backend)
