"""Live integration tests against the Duffel test API.

Run with:  .venv/bin/pytest tests/test_live.py -v
Requires:  a file called `test_api_key` in the project root containing a Duffel test token.
"""

from datetime import date, timedelta
from pathlib import Path

import pytest

from duffelbag import AsyncDuffel, Duffel
from duffelbag.models import (
    Aircraft,
    Airline,
    Airport,
    City,
    LoyaltyProgramme,
    Offer,
    OfferRequest,
    Order,
    OrderCancellation,
    Place,
    SeatMap,
)

PROJECT_ROOT = Path(__file__).resolve().parent.parent
API_KEY_FILE = PROJECT_ROOT / "test_api_key"

# Departure date 30 days out so offers remain valid
DEPARTURE_DATE = (date.today() + timedelta(days=30)).isoformat()


def _read_api_key() -> str:
    if not API_KEY_FILE.exists():
        pytest.skip("test_api_key file not found — skipping live tests")
    return API_KEY_FILE.read_text().strip()


@pytest.fixture(scope="module")
def api_key() -> str:
    return _read_api_key()


@pytest.fixture(scope="module")
def client(api_key) -> Duffel:
    c = Duffel(token=api_key)
    yield c
    c.close()


@pytest.fixture
async def async_client(api_key) -> AsyncDuffel:
    client = AsyncDuffel(token=api_key)
    yield client
    await client.close()


# ---------------------------------------------------------------------------
# Supporting resources (read-only)
# ---------------------------------------------------------------------------


class TestAirlines:
    def test_list(self, client):
        airlines = client.airlines.list(limit=5).page()
        assert len(airlines) > 0
        assert all(isinstance(a, Airline) for a in airlines)
        assert airlines[0].id is not None

    def test_get(self, client):
        # British Airways is always present in Duffel
        ba = client.airlines.get("arl_00009VME7DBKeMags5CliQ")
        assert isinstance(ba, Airline)
        assert ba.iata_code == "BA"


class TestAircraft:
    def test_list(self, client):
        aircraft = client.aircraft.list(limit=5).page()
        assert len(aircraft) > 0
        assert all(isinstance(a, Aircraft) for a in aircraft)

    def test_get(self, client):
        page = client.aircraft.list(limit=1).page()
        ac = client.aircraft.get(page[0].id)
        assert isinstance(ac, Aircraft)
        assert ac.id == page[0].id


class TestAirports:
    def test_list(self, client):
        airports = client.airports.list(limit=5).page()
        assert len(airports) > 0
        assert all(isinstance(a, Airport) for a in airports)

    def test_list_by_country(self, client):
        airports = client.airports.list(limit=5, iata_country_code="GB").page()
        assert len(airports) > 0
        assert all(a.iata_country_code == "GB" for a in airports)

    def test_get(self, client):
        lhr = client.airports.get("arp_lhr_gb")
        assert isinstance(lhr, Airport)
        assert lhr.iata_code == "LHR"


class TestCities:
    def test_list(self, client):
        cities = client.cities.list(limit=5).page()
        assert len(cities) > 0
        assert all(isinstance(c, City) for c in cities)

    def test_get(self, client):
        page = client.cities.list(limit=1).page()
        city = client.cities.get(page[0].id)
        assert isinstance(city, City)
        assert city.id == page[0].id


class TestPlaces:
    def test_suggest(self, client):
        places = client.places.suggest("Heathrow")
        assert len(places) > 0
        assert all(isinstance(p, Place) for p in places)
        iata_codes = [p.iata_code for p in places if p.iata_code]
        assert "LHR" in iata_codes


class TestLoyaltyProgrammes:
    def test_list(self, client):
        programmes = client.loyalty_programmes.list(limit=5).page()
        assert len(programmes) > 0
        assert all(isinstance(p, LoyaltyProgramme) for p in programmes)


# ---------------------------------------------------------------------------
# Flight search flow
# ---------------------------------------------------------------------------


@pytest.fixture(scope="module")
def offer_request_with_offers(client) -> OfferRequest:
    """Create an offer request and return it with offers inline."""
    return client.offer_requests.create(
        slices=[{
            "origin": "LHR",
            "destination": "JFK",
            "departure_date": DEPARTURE_DATE,
        }],
        passengers=[{"type": "adult"}],
        cabin_class="economy",
        return_offers=True,
    )


class TestOfferRequests:
    def test_create(self, offer_request_with_offers):
        ofr = offer_request_with_offers
        assert isinstance(ofr, OfferRequest)
        assert ofr.id.startswith("orq_")
        assert ofr.offers is not None
        assert len(ofr.offers) > 0

    def test_get(self, client, offer_request_with_offers):
        ofr = client.offer_requests.get(offer_request_with_offers.id)
        assert isinstance(ofr, OfferRequest)
        assert ofr.id == offer_request_with_offers.id

    def test_list(self, client):
        page = client.offer_requests.list(limit=2).page()
        assert len(page) > 0
        assert all(isinstance(o, OfferRequest) for o in page)


class TestOffers:
    def test_list(self, client, offer_request_with_offers):
        offers = client.offers.list(offer_request_with_offers.id, limit=5).page()
        assert len(offers) > 0
        assert all(isinstance(o, Offer) for o in offers)

    def test_get(self, client, offer_request_with_offers):
        first_offer = offer_request_with_offers.offers[0]
        offer = client.offers.get(first_offer.id)
        assert isinstance(offer, Offer)
        assert offer.id == first_offer.id
        assert offer.total_amount is not None

    def test_list_sorted(self, client, offer_request_with_offers):
        offers = client.offers.list(
            offer_request_with_offers.id, limit=5, sort="total_amount",
        ).page()
        assert len(offers) > 0
        amounts = [float(o.total_amount) for o in offers]
        assert amounts == sorted(amounts)


class TestSeatMaps:
    def test_list(self, client, offer_request_with_offers):
        offer = offer_request_with_offers.offers[0]
        seat_maps = client.seat_maps.list(offer.id)
        assert isinstance(seat_maps, list)
        # Some test offers may not have seat maps; just verify the call works
        if seat_maps:
            assert all(isinstance(sm, SeatMap) for sm in seat_maps)


# ---------------------------------------------------------------------------
# Full booking flow: search -> book -> cancel
# ---------------------------------------------------------------------------


class TestBookingFlow:
    @pytest.fixture(scope="class")
    def booked_order(self, client) -> Order:
        """Search, book, and return an order using Duffel's test airline."""
        ofr = client.offer_requests.create(
            slices=[{
                "origin": "LHR",
                "destination": "JFK",
                "departure_date": DEPARTURE_DATE,
            }],
            passengers=[{"type": "adult"}],
            return_offers=True,
        )

        # Pick the cheapest offer
        offers = sorted(ofr.offers, key=lambda o: float(o.total_amount))
        offer = offers[0]
        passenger = offer.passengers[0]

        order = client.orders.create(
            selected_offers=[offer.id],
            passengers=[{
                "id": passenger.id,
                "title": "mr",
                "gender": "m",
                "given_name": "Test",
                "family_name": "Traveller",
                "born_on": "1990-01-01",
                "email": "test@example.com",
                "phone_number": "+442080000000",
            }],
            type="instant",
            payments=[{
                "type": "balance",
                "amount": offer.total_amount,
                "currency": offer.total_currency,
            }],
        )
        return order

    def test_order_created(self, booked_order):
        assert isinstance(booked_order, Order)
        assert booked_order.id.startswith("ord_")
        assert booked_order.booking_reference is not None

    def test_order_get(self, client, booked_order):
        order = client.orders.get(booked_order.id)
        assert isinstance(order, Order)
        assert order.id == booked_order.id

    def test_order_list(self, client, booked_order):
        orders = client.orders.list(limit=2).page()
        assert len(orders) > 0
        assert all(isinstance(o, Order) for o in orders)

    def test_order_update_metadata(self, client, booked_order):
        updated = client.orders.update(
            booked_order.id, metadata={"test_key": "test_value"},
        )
        assert isinstance(updated, Order)
        assert updated.metadata.get("test_key") == "test_value"

    def test_order_cancellation(self, client, booked_order):
        cancellation = client.order_cancellations.create(order_id=booked_order.id)
        assert isinstance(cancellation, OrderCancellation)
        assert cancellation.id.startswith("ore_")

        confirmed = client.order_cancellations.confirm(cancellation.id)
        assert isinstance(confirmed, OrderCancellation)


# ---------------------------------------------------------------------------
# Async parity — verify the async client works for key operations
# ---------------------------------------------------------------------------


class TestAsync:
    async def test_airlines_list(self, async_client):
        airlines = await async_client.airlines.list(limit=3).page()
        assert len(airlines) > 0
        assert all(isinstance(a, Airline) for a in airlines)

    async def test_airline_get(self, async_client):
        ba = await async_client.airlines.get("arl_00009VME7DBKeMags5CliQ")
        assert isinstance(ba, Airline)
        assert ba.iata_code == "BA"

    async def test_airports_list(self, async_client):
        airports = await async_client.airports.list(limit=3).page()
        assert len(airports) > 0
        assert all(isinstance(a, Airport) for a in airports)

    async def test_places_suggest(self, async_client):
        places = await async_client.places.suggest("London")
        assert len(places) > 0
        assert all(isinstance(p, Place) for p in places)

    async def test_offer_request_create(self, async_client):
        ofr = await async_client.offer_requests.create(
            slices=[{
                "origin": "LHR",
                "destination": "CDG",
                "departure_date": DEPARTURE_DATE,
            }],
            passengers=[{"type": "adult"}],
            return_offers=True,
        )
        assert isinstance(ofr, OfferRequest)
        assert ofr.id.startswith("orq_")
        assert len(ofr.offers) > 0

        # Verify we can list offers via the async iterator
        count = 0
        async for offer in async_client.offers.list(ofr.id, limit=3):
            assert isinstance(offer, Offer)
            count += 1
            if count >= 3:
                break
        assert count > 0
