"""Integration tests against a running local API server.

Run with:
    pytest tests/test_integration.py -v

Requires the platform-api running on localhost:8000.
"""

from __future__ import annotations

import pytest
import pytest_asyncio

from rulebook import (
    AsyncRulebook,
    Rulebook,
    AuthenticationError,
    NotFoundError,
    PermissionDeniedError,
    UnprocessableEntityError,
)
from rulebook._response import APIResponse
from rulebook.types import (
    Exchange,
    ExchangeDetail,
    DateRange,
    FeeScheduleResult,
    FeeScheduleResultFilters,
    PaginatedResponse,
)

BASE_URL = "https://dev-be-stock-rule-book.codeaza.org/api/v1"
API_KEY = "1ea877ead3584934b00ea7205cc785d7"

# Known data for Waleed's account (access to "Fee - CBOE US Options" only)
EXCHANGE_NAME = "fee_cboe_us_options"
VALID_RESULT_ID = "6eca1ca4-1ece-4dc3-bc2c-34d7c9df4b40"
VALID_VERSION_ID = "c9ffbb45-cfe9-4b16-b4cd-3587e4c29d3c"
FORBIDDEN_RESULT_ID = "41d9eed9-56cf-447a-975e-d4f1cda010a3"
FORBIDDEN_VERSION_ID = "75d72b98-e065-4ce1-93b0-3f161c9d9bae"
NONEXISTENT_UUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"


@pytest.fixture
def client() -> Rulebook:
    c = Rulebook(api_key=API_KEY, base_url=BASE_URL, max_retries=0, timeout=60.0)
    yield c
    c.close()


@pytest.fixture
def bad_client() -> Rulebook:
    c = Rulebook(api_key="bad-key", base_url=BASE_URL, max_retries=0, timeout=60.0)
    yield c
    c.close()


@pytest_asyncio.fixture
async def async_client() -> AsyncRulebook:
    c = AsyncRulebook(api_key=API_KEY, base_url=BASE_URL, max_retries=0, timeout=60.0)
    yield c
    await c.close()


# ===================================================================
# Exchanges — list
# ===================================================================


class TestExchangesListIntegration:

    def test_list_returns_exchanges(self, client):
        result = client.exchanges.list()

        assert isinstance(result, list)
        assert len(result) >= 1

        ex = result[0]
        assert isinstance(ex, Exchange)
        assert ex.name == EXCHANGE_NAME
        assert ex.display_name == "Fee - CBOE US Options"
        assert "OPTION" in ex.fee_types
        assert ex.record_count > 0

    def test_list_unauthenticated(self, bad_client):
        with pytest.raises(AuthenticationError) as exc_info:
            bad_client.exchanges.list()

        assert exc_info.value.status_code == 401


# ===================================================================
# Exchanges — retrieve
# ===================================================================


class TestExchangesRetrieveIntegration:

    def test_retrieve_success(self, client):
        detail = client.exchanges.retrieve(EXCHANGE_NAME)

        assert isinstance(detail, ExchangeDetail)
        assert detail.name == EXCHANGE_NAME
        assert detail.display_name == "Fee - CBOE US Options"
        assert isinstance(detail.date_range, DateRange)
        assert detail.date_range.earliest  # non-empty string
        assert detail.date_range.latest
        assert "OPTION" in detail.fee_types
        assert len(detail.fee_categories) > 0
        assert len(detail.actions) > 0
        assert len(detail.participants) > 0
        assert len(detail.symbol_classifications) > 0
        assert len(detail.symbol_types) > 0
        assert len(detail.trade_types) > 0
        assert detail.record_count > 0

    def test_retrieve_not_found(self, client):
        with pytest.raises(NotFoundError) as exc_info:
            client.exchanges.retrieve("nonexistent_exchange")

        assert exc_info.value.status_code == 404

    def test_retrieve_unauthenticated(self, bad_client):
        with pytest.raises(AuthenticationError):
            bad_client.exchanges.retrieve(EXCHANGE_NAME)

    def test_retrieve_empty_name_raises_locally(self, client):
        with pytest.raises(ValueError, match="non-empty"):
            client.exchanges.retrieve("")


# ===================================================================
# Exchanges — with_raw_response
# ===================================================================


class TestExchangesRawResponseIntegration:

    def test_raw_response_list(self, client):
        raw = client.exchanges.with_raw_response.list()

        assert isinstance(raw, APIResponse)
        assert raw.status_code == 200
        assert "application/json" in raw.headers["content-type"]

        parsed = raw.parse()
        assert isinstance(parsed, list)
        assert len(parsed) >= 1
        assert isinstance(parsed[0], Exchange)

    def test_raw_response_retrieve(self, client):
        raw = client.exchanges.with_raw_response.retrieve(EXCHANGE_NAME)

        assert isinstance(raw, APIResponse)
        assert raw.status_code == 200

        detail = raw.parse()
        assert isinstance(detail, ExchangeDetail)
        assert detail.name == EXCHANGE_NAME


# ===================================================================
# Exchanges — async
# ===================================================================


class TestAsyncExchangesIntegration:

    @pytest.mark.asyncio
    async def test_async_list(self, async_client):
        result = await async_client.exchanges.list()

        assert isinstance(result, list)
        assert len(result) >= 1
        assert isinstance(result[0], Exchange)

    @pytest.mark.asyncio
    async def test_async_retrieve(self, async_client):
        detail = await async_client.exchanges.retrieve(EXCHANGE_NAME)

        assert isinstance(detail, ExchangeDetail)
        assert detail.name == EXCHANGE_NAME

    @pytest.mark.asyncio
    async def test_async_retrieve_not_found(self, async_client):
        with pytest.raises(NotFoundError):
            await async_client.exchanges.retrieve("nonexistent_exchange")


# ===================================================================
# Client — per-request overrides
# ===================================================================


class TestPerRequestOverridesIntegration:

    def test_extra_headers(self, client):
        # Extra headers should not break the request
        result = client.exchanges.list(extra_headers={"X-Custom-Test": "integration"})
        assert isinstance(result, list)

    def test_timeout_override(self, client):
        result = client.exchanges.list(timeout=120.0)
        assert isinstance(result, list)

    def test_with_options(self, client):
        client2 = client.with_options(timeout=120.0)
        result = client2.exchanges.list()
        assert isinstance(result, list)
        client2.close()

    def test_context_manager(self):
        with Rulebook(api_key=API_KEY, base_url=BASE_URL, timeout=60.0) as c:
            result = c.exchanges.list()
            assert isinstance(result, list)


# ===================================================================
# Fee schedule results — list
# ===================================================================


class TestFeeScheduleResultsListIntegration:

    def test_list_returns_paginated_results(self, client):
        result = client.fee_schedule_results.list(page_size=5)

        assert isinstance(result, PaginatedResponse)
        assert result.total_records > 0
        assert result.page_size == 5
        assert result.total_pages >= 1
        assert result.current_page == 0
        assert len(result.data) <= 5

        item = result.data[0]
        assert isinstance(item, FeeScheduleResult)
        assert item.id
        assert item.exchange_name
        assert item.fee_type
        assert item.fee_category

    def test_list_with_exchange_filter(self, client):
        result = client.fee_schedule_results.list(
            exchange_name=["fee_cboe_us_options"],
            page_size=5,
        )

        assert isinstance(result, PaginatedResponse)
        for item in result.data:
            assert item.exchange_name == "fee_cboe_us_options"

    def test_list_empty_page(self, client):
        result = client.fee_schedule_results.list(page_number=99999, page_size=100)

        assert isinstance(result, PaginatedResponse)
        assert result.data == []

    def test_list_latest_only(self, client):
        result = client.fee_schedule_results.list(latest_only=True, page_size=5)

        assert isinstance(result, PaginatedResponse)
        # Should return results (at least for the accessible exchange)
        assert result.total_records >= 0

    def test_list_unauthenticated(self, bad_client):
        with pytest.raises(AuthenticationError) as exc_info:
            bad_client.fee_schedule_results.list()

        assert exc_info.value.status_code == 401


# ===================================================================
# Fee schedule results — retrieve
# ===================================================================


class TestFeeScheduleResultsRetrieveIntegration:

    def test_retrieve_success(self, client):
        result = client.fee_schedule_results.retrieve(VALID_RESULT_ID)

        assert isinstance(result, FeeScheduleResult)
        assert result.id == VALID_RESULT_ID
        assert result.exchange_name
        assert result.fee_type
        assert result.fee_category

    def test_retrieve_not_found(self, client):
        with pytest.raises(NotFoundError) as exc_info:
            client.fee_schedule_results.retrieve(NONEXISTENT_UUID)

        assert exc_info.value.status_code == 404

    def test_retrieve_forbidden(self, client):
        with pytest.raises(PermissionDeniedError) as exc_info:
            client.fee_schedule_results.retrieve(FORBIDDEN_RESULT_ID)

        assert exc_info.value.status_code == 403

    def test_retrieve_unauthenticated(self, bad_client):
        with pytest.raises(AuthenticationError):
            bad_client.fee_schedule_results.retrieve(VALID_RESULT_ID)

    def test_retrieve_empty_id_raises_locally(self, client):
        with pytest.raises(ValueError, match="non-empty"):
            client.fee_schedule_results.retrieve("")


# ===================================================================
# Fee schedule results — get_filters
# ===================================================================


class TestFeeScheduleResultsGetFiltersIntegration:

    def test_get_filters_success(self, client):
        filters = client.fee_schedule_results.get_filters()

        assert isinstance(filters, FeeScheduleResultFilters)
        assert len(filters.exchange_names) >= 1
        assert len(filters.fee_types) >= 1
        assert len(filters.fee_categories) >= 1
        assert len(filters.fee_actions) >= 1
        assert len(filters.fee_participants) >= 1

    def test_get_filters_unauthenticated(self, bad_client):
        with pytest.raises(AuthenticationError):
            bad_client.fee_schedule_results.get_filters()


# ===================================================================
# Fee schedule results — get_results_by_version
# ===================================================================


class TestFeeScheduleResultsByVersionIntegration:

    def test_get_results_by_version_success(self, client):
        result = client.fee_schedule_results.get_results_by_version(
            VALID_VERSION_ID, page_size=5
        )

        assert isinstance(result, PaginatedResponse)
        assert result.total_records > 0
        assert len(result.data) <= 5
        for item in result.data:
            assert isinstance(item, FeeScheduleResult)
            assert item.version_id == VALID_VERSION_ID

    def test_get_results_by_version_not_found(self, client):
        with pytest.raises(NotFoundError):
            client.fee_schedule_results.get_results_by_version(NONEXISTENT_UUID)

    def test_get_results_by_version_forbidden(self, client):
        with pytest.raises(PermissionDeniedError):
            client.fee_schedule_results.get_results_by_version(FORBIDDEN_VERSION_ID)

    def test_get_results_by_version_empty_id_raises_locally(self, client):
        with pytest.raises(ValueError, match="non-empty"):
            client.fee_schedule_results.get_results_by_version("")


# ===================================================================
# Fee schedule results — with_raw_response
# ===================================================================


class TestFeeScheduleResultsRawResponseIntegration:

    def test_raw_response_list(self, client):
        raw = client.fee_schedule_results.with_raw_response.list(page_size=5)

        assert isinstance(raw, APIResponse)
        assert raw.status_code == 200
        assert "application/json" in raw.headers["content-type"]

        parsed = raw.parse()
        assert isinstance(parsed, PaginatedResponse)
        assert len(parsed.data) >= 1
        assert isinstance(parsed.data[0], FeeScheduleResult)

    def test_raw_response_retrieve(self, client):
        raw = client.fee_schedule_results.with_raw_response.retrieve(VALID_RESULT_ID)

        assert isinstance(raw, APIResponse)
        assert raw.status_code == 200

        result = raw.parse()
        assert isinstance(result, FeeScheduleResult)
        assert result.id == VALID_RESULT_ID

    def test_raw_response_get_filters(self, client):
        raw = client.fee_schedule_results.with_raw_response.get_filters()

        assert isinstance(raw, APIResponse)
        assert raw.status_code == 200

        filters = raw.parse()
        assert isinstance(filters, FeeScheduleResultFilters)
        assert len(filters.exchange_names) >= 1


# ===================================================================
# Fee schedule results — async
# ===================================================================


class TestAsyncFeeScheduleResultsIntegration:

    @pytest.mark.asyncio
    async def test_async_list(self, async_client):
        result = await async_client.fee_schedule_results.list(page_size=5)

        assert isinstance(result, PaginatedResponse)
        assert result.total_records > 0
        assert isinstance(result.data[0], FeeScheduleResult)

    @pytest.mark.asyncio
    async def test_async_retrieve(self, async_client):
        result = await async_client.fee_schedule_results.retrieve(VALID_RESULT_ID)

        assert isinstance(result, FeeScheduleResult)
        assert result.id == VALID_RESULT_ID

    @pytest.mark.asyncio
    async def test_async_retrieve_not_found(self, async_client):
        with pytest.raises(NotFoundError):
            await async_client.fee_schedule_results.retrieve(NONEXISTENT_UUID)

    @pytest.mark.asyncio
    async def test_async_get_filters(self, async_client):
        filters = await async_client.fee_schedule_results.get_filters()

        assert isinstance(filters, FeeScheduleResultFilters)
        assert len(filters.exchange_names) >= 1

    @pytest.mark.asyncio
    async def test_async_get_results_by_version(self, async_client):
        result = await async_client.fee_schedule_results.get_results_by_version(
            VALID_VERSION_ID, page_size=5
        )

        assert isinstance(result, PaginatedResponse)
        assert result.total_records > 0
