"""Tests for Pydantic models."""

from ticksupply.models.catalog import (
    DatastreamInfo,
    Exchange,
    Instrument,
    PaginatedInstruments,
)
from ticksupply.models.common import ErrorResponse
from ticksupply.models.export import (
    AvailabilityRange,
    ExportJob,
    ExportStatus,
)
from ticksupply.models.subscription import (
    Subscription,
    SubscriptionSpan,
    SubscriptionStatus,
)


class TestCommonModels:
    """Test common models."""

    def test_error_response(self) -> None:
        """ErrorResponse parses correctly."""
        data = {
            "error": {
                "code": "not_found",
                "message": "Resource not found",
                "trace_id": "trace-123",
            }
        }
        response = ErrorResponse.model_validate(data)
        assert response.error.code == "not_found"
        assert response.error.message == "Resource not found"
        assert response.error.trace_id == "trace-123"

    def test_error_response_without_trace_id(self) -> None:
        """ErrorResponse works without trace_id."""
        data = {
            "error": {
                "code": "internal",
                "message": "Server error",
            }
        }
        response = ErrorResponse.model_validate(data)
        assert response.error.trace_id is None


class TestCatalogModels:
    """Test catalog models."""

    def test_exchange(self) -> None:
        """Exchange model parses correctly."""
        data = {
            "code": "binance",
            "display_name": "Binance",
        }
        exchange = Exchange.model_validate(data)
        assert exchange.code == "binance"
        assert exchange.display_name == "Binance"

    def test_instrument(self) -> None:
        """Instrument model parses correctly."""
        data = {
            "symbol": "BTCUSDT",
            "base": "BTC",
            "quote": "USDT",
            "instrument_type": "spot",
        }
        instrument = Instrument.model_validate(data)
        assert instrument.symbol == "BTCUSDT"
        assert instrument.base == "BTC"
        assert instrument.quote == "USDT"
        assert instrument.instrument_type == "spot"

    def test_instrument_optional_fields(self) -> None:
        """Instrument model works with optional fields."""
        data = {
            "symbol": "BTCUSDT",
        }
        instrument = Instrument.model_validate(data)
        assert instrument.base is None
        assert instrument.quote is None
        assert instrument.instrument_type is None

    def test_datastream_info(self) -> None:
        """DatastreamInfo model parses correctly."""
        data = {
            "datastream_id": 100,
            "exchange": "binance",
            "instrument": "BTCUSDT",
            "stream_type": "trades",
            "wire_format": "json",
        }
        ds = DatastreamInfo.model_validate(data)
        assert ds.datastream_id == 100
        assert ds.exchange == "binance"
        assert ds.instrument == "BTCUSDT"
        assert ds.stream_type == "trades"
        assert ds.wire_format == "json"

    def test_paginated_instruments(self) -> None:
        """PaginatedInstruments model parses correctly."""
        data = {
            "items": [
                {"symbol": "BTCUSDT", "base": "BTC", "quote": "USDT"},
                {"symbol": "ETHUSDT", "base": "ETH", "quote": "USDT"},
            ],
            "total": 100,
            "limit": 50,
            "next_page_token": "token123",
        }
        page = PaginatedInstruments.model_validate(data)
        assert len(page.items) == 2
        assert page.items[0].symbol == "BTCUSDT"
        assert page.total == 100
        assert page.has_next is True

    def test_paginated_no_next_page(self) -> None:
        """Paginated model has_next is False when no token."""
        data = {
            "items": [],
            "total": 0,
            "limit": 50,
            "next_page_token": None,
        }
        page = PaginatedInstruments.model_validate(data)
        assert page.has_next is False


class TestSubscriptionModels:
    """Test subscription models."""

    def test_subscription_status_enum(self) -> None:
        """SubscriptionStatus enum values."""
        assert SubscriptionStatus.ACTIVE == "active"
        assert SubscriptionStatus.PAUSED == "paused"
        assert SubscriptionStatus.DELETED == "deleted"

    def test_subscription_span(self) -> None:
        """SubscriptionSpan model parses correctly."""
        data = {
            "id": "spn_0194a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
            "started_at": "2025-01-01T00:00:00Z",
            "ended_at": "2025-01-02T00:00:00Z",
        }
        span = SubscriptionSpan.model_validate(data)
        assert span.id == "spn_0194a1b2c3d4e5f6a7b8c9d0e1f2a3b4"
        assert span.ended_at is not None

    def test_subscription_span_ongoing(self) -> None:
        """SubscriptionSpan with no ended_at."""
        data = {
            "id": "spn_0194a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
            "started_at": "2025-01-01T00:00:00Z",
            "ended_at": None,
        }
        span = SubscriptionSpan.model_validate(data)
        assert span.ended_at is None

    def test_subscription(self) -> None:
        """Subscription model parses correctly."""
        data = {
            "id": "sub_550e8400e29b41d4a716446655440000",
            "datastream_id": 1001,
            "status": "active",
            "created_at": "2025-01-01T00:00:00Z",
            "datastream": None,
        }
        sub = Subscription.model_validate(data)
        assert sub.id == "sub_550e8400e29b41d4a716446655440000"
        assert sub.status == SubscriptionStatus.ACTIVE
        assert sub.datastream is None


class TestExportModels:
    """Test export models."""

    def test_export_status_enum(self) -> None:
        """ExportStatus enum values."""
        assert ExportStatus.QUEUED == "queued"
        assert ExportStatus.RUNNING == "running"
        assert ExportStatus.SUCCEEDED == "succeeded"
        assert ExportStatus.FAILED == "failed"
        assert ExportStatus.CANCELED == "canceled"

    def test_export_job(self) -> None:
        """ExportJob model parses correctly."""
        data = {
            "id": "exp_123",
            "datastream_id": 100,
            "start_time": 1704067200000000000,
            "end_time": 1704153600000000000,
            "format": "csv",
            "status": "succeeded",
            "created_at": "2025-01-01T00:00:00Z",
            "finished_at": "2025-01-01T00:05:00Z",
        }
        job = ExportJob.model_validate(data)
        assert job.id == "exp_123"
        assert job.datastream_id == 100
        assert job.format == "csv"
        assert job.status == ExportStatus.SUCCEEDED
        assert job.finished_at is not None

    def test_availability_range_from_to_mapping(self) -> None:
        """AvailabilityRange handles 'from' and 'to' Python keywords."""
        data = {
            "from": 1704067200000000000,  # nanoseconds since Unix epoch
            "to": 1704153600000000000,
            "rows_estimate": 1000000,
        }
        range_ = AvailabilityRange.model_validate(data)
        assert range_.from_ns == 1704067200000000000
        assert range_.to_ns == 1704153600000000000
        assert range_.rows_estimate == 1000000
