from __future__ import annotations

import json

from pytest_httpx import HTTPXMock

from ebay_client.account.client import AccountClient
from ebay_client.core.auth.models import OAuthToken
from ebay_client.core.http.transport import ApiTransport
from ebay_client.feed.client import CreatedFeedResource, FeedClient, FeedFileDownload
from ebay_client.fulfillment.client import FulfillmentClient
from ebay_client.generated.account.models import (
    FulfillmentPolicy,
    FulfillmentPolicyRequest,
    PaymentPolicy,
    PaymentPolicyRequest,
    Programs,
    ReturnPolicy,
    ReturnPolicyRequest,
)
from ebay_client.generated.feed.models import TaskCollection
from ebay_client.generated.feed.models import (
    CreateTaskRequest,
    CreateUserScheduleRequest,
    ScheduleTemplateResponse,
    UpdateUserScheduleRequest,
    UserScheduleResponse,
)
from ebay_client.generated.fulfillment.models import Order
from ebay_client.generated.inventory.models import InventoryItemWithSkuLocaleGroupid
from ebay_client.generated.media.models import (
    CreateDocumentFromUrlRequest,
    CreateDocumentRequest,
    CreateDocumentResponse,
    CreateImageFromUrlRequest,
    CreateVideoRequest,
    DocumentResponse,
    ImageResponse,
    Video,
)
from ebay_client.generated.notification.models import (
    Config,
    CreateSubscriptionFilterRequest,
    CreateSubscriptionRequest,
    DeliveryConfig,
    Subscription,
    SubscriptionFilter,
    SubscriptionPayloadDetail,
    DestinationRequest,
    TopicSearchResponse,
    UpdateSubscriptionRequest,
)
from ebay_client.inventory.client import InventoryClient
from ebay_client.media.client import (
    CreatedMediaResource,
    DocumentWorkflowResult,
    MediaClient,
    VideoWorkflowResult,
    extract_resource_id,
    guess_media_content_type,
)
from ebay_client.notification.client import NotificationClient


class DummyOAuthClient:
    def get_valid_token(
        self,
        *,
        scopes: list[str] | None = None,
        scope_options: list[list[str]] | None = None,
    ) -> OAuthToken:
        if scopes:
            resolved_scopes = scopes
        elif scope_options:
            resolved_scopes = scope_options[0]
        else:
            resolved_scopes = []
        return OAuthToken(access_token="test-token", scope=" ".join(resolved_scopes))


def build_transport() -> ApiTransport:
    return ApiTransport(base_url="https://api.ebay.com", oauth_client=DummyOAuthClient())


class RecordingOAuthClient:
    def __init__(self) -> None:
        self.last_scopes: list[str] | None = None
        self.last_scope_options: list[list[str]] | None = None

    def get_valid_token(
        self,
        *,
        scopes: list[str] | None = None,
        scope_options: list[list[str]] | None = None,
    ) -> OAuthToken:
        self.last_scopes = scopes
        self.last_scope_options = scope_options
        resolved_scopes = scopes or (scope_options[0] if scope_options else [])
        return OAuthToken(access_token="recorded-token", scope=" ".join(resolved_scopes))


def test_notification_wrapper_returns_pydantic_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/notification/v1/topic?limit=10",
        json={"topics": [{"topicId": "MARKETPLACE_ACCOUNT_DELETION", "description": "topic"}], "total": 1},
    )

    client = NotificationClient(build_transport())
    result = client.get_topics(limit=10)

    assert isinstance(result, TopicSearchResponse)
    assert result.total == 1
    assert result.topics and result.topics[0].topicId == "MARKETPLACE_ACCOUNT_DELETION"
    request = httpx_mock.get_requests()[0]
    assert request.headers["Authorization"] == "Bearer test-token"


def test_notification_wrapper_serializes_typed_request_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/notification/v1/destination",
        json={},
        status_code=201,
    )

    client = NotificationClient(build_transport())
    payload = DestinationRequest(
        name="main-destination",
        status="ENABLED",
        deliveryConfig=DeliveryConfig(
            endpoint="https://example.test/webhook",
            verificationToken="verification_token_1234567890123456",
        ),
    )

    client.create_destination(payload)

    request = httpx_mock.get_requests()[0]
    body = json.loads(request.content.decode("utf-8"))
    assert body["name"] == "main-destination"
    assert body["deliveryConfig"]["endpoint"] == "https://example.test/webhook"


def test_notification_config_wrapper_round_trip(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/notification/v1/config",
        json={"alertEmail": "alerts@example.test"},
    )
    httpx_mock.add_response(
        method="PUT",
        url="https://api.ebay.com/commerce/notification/v1/config",
        status_code=204,
    )

    client = NotificationClient(build_transport())
    config = client.get_config()
    client.update_config(Config(alertEmail="ops@example.test"))

    assert isinstance(config, Config)
    assert config.alertEmail == "alerts@example.test"
    request = httpx_mock.get_requests()[1]
    body = json.loads(request.content.decode("utf-8"))
    assert body["alertEmail"] == "ops@example.test"


def test_notification_subscription_wrapper_serializes_and_returns_models(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/notification/v1/subscription",
        json={},
        status_code=201,
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1",
        json={"subscriptionId": "SUB-1", "topicId": "TOPIC-1", "status": "ENABLED"},
    )
    httpx_mock.add_response(
        method="PUT",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1",
        status_code=204,
    )
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/enable",
        status_code=204,
    )
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/test",
        status_code=202,
    )

    client = NotificationClient(build_transport())
    create_payload = CreateSubscriptionRequest(
        destinationId="DEST-1",
        topicId="TOPIC-1",
        status="DISABLED",
        payload=SubscriptionPayloadDetail(
            deliveryProtocol="HTTPS",
            format="JSON",
            schemaVersion="1.0",
        ),
    )
    update_payload = UpdateSubscriptionRequest(status="ENABLED", destinationId="DEST-1")

    client.create_subscription(create_payload)
    subscription = client.get_subscription("SUB-1")
    client.update_subscription("SUB-1", update_payload)
    client.enable_subscription("SUB-1")
    client.test_subscription("SUB-1")

    assert isinstance(subscription, Subscription)
    assert subscription.subscriptionId == "SUB-1"
    create_request = httpx_mock.get_requests()[0]
    create_body = json.loads(create_request.content.decode("utf-8"))
    assert create_body["destinationId"] == "DEST-1"
    assert create_body["payload"]["deliveryProtocol"] == "HTTPS"
    update_request = httpx_mock.get_requests()[2]
    update_body = json.loads(update_request.content.decode("utf-8"))
    assert update_body == {"destinationId": "DEST-1", "status": "ENABLED"}


def test_notification_subscription_filter_wrapper_serializes_and_returns_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter",
        json={},
        status_code=201,
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter/FILTER-1",
        json={"filterId": "FILTER-1", "subscriptionId": "SUB-1", "filterStatus": "ENABLED"},
    )
    httpx_mock.add_response(
        method="DELETE",
        url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter/FILTER-1",
        status_code=204,
    )

    client = NotificationClient(build_transport())
    payload = CreateSubscriptionFilterRequest(
        filterSchema={
            "properties": {
                "data": {
                    "type": "object",
                }
            }
        }
    )

    client.create_subscription_filter("SUB-1", payload)
    subscription_filter = client.get_subscription_filter("SUB-1", "FILTER-1")
    client.delete_subscription_filter("SUB-1", "FILTER-1")

    assert isinstance(subscription_filter, SubscriptionFilter)
    assert subscription_filter.filterId == "FILTER-1"
    request = httpx_mock.get_requests()[0]
    body = json.loads(request.content.decode("utf-8"))
    assert body["filterSchema"]["properties"]["data"]["type"] == "object"


def test_inventory_wrapper_returns_inventory_item_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
        json={"sku": "SKU-1"},
    )

    client = InventoryClient(build_transport())
    result = client.get_inventory_item("SKU-1")

    assert isinstance(result, InventoryItemWithSkuLocaleGroupid)
    assert result.sku == "SKU-1"


def test_fulfillment_wrapper_returns_order_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
        json={"orderId": "ORDER-1"},
    )

    client = FulfillmentClient(build_transport())
    result = client.get_order("ORDER-1")

    assert isinstance(result, Order)
    assert result.orderId == "ORDER-1"


def test_account_wrapper_returns_programs_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/account/v1/program/get_opted_in_programs",
        json={"programs": [{"programType": "OUT_OF_STOCK_CONTROL"}]},
    )

    client = AccountClient(build_transport())
    result = client.get_opted_in_programs()

    assert isinstance(result, Programs)
    assert result.programs and result.programs[0].programType == "OUT_OF_STOCK_CONTROL"


def test_account_wrapper_returns_policy_models_by_id_and_name(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/account/v1/fulfillment_policy/FULFILL-1",
        json={"fulfillmentPolicyId": "FULFILL-1", "name": "Fast shipping"},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/account/v1/payment_policy/get_by_policy_name?marketplace_id=EBAY_US&name=Default%20payment",
        json={"paymentPolicyId": "PAY-1", "name": "Default payment"},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/account/v1/return_policy/get_by_policy_name?marketplace_id=EBAY_US&name=30%20day%20returns",
        json={"returnPolicyId": "RET-1", "name": "30 day returns"},
    )

    client = AccountClient(build_transport())
    fulfillment = client.get_fulfillment_policy("FULFILL-1")
    payment = client.get_payment_policy_by_name(marketplace_id="EBAY_US", name="Default payment")
    returns = client.get_return_policy_by_name(marketplace_id="EBAY_US", name="30 day returns")

    assert isinstance(fulfillment, FulfillmentPolicy)
    assert fulfillment.fulfillmentPolicyId == "FULFILL-1"
    assert isinstance(payment, PaymentPolicy)
    assert payment.paymentPolicyId == "PAY-1"
    assert isinstance(returns, ReturnPolicy)
    assert returns.returnPolicyId == "RET-1"


def test_account_wrapper_serializes_policy_requests_and_delete(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/sell/account/v1/payment_policy",
        json={"paymentPolicyId": "PAY-NEW"},
        status_code=201,
    )
    httpx_mock.add_response(
        method="PUT",
        url="https://api.ebay.com/sell/account/v1/fulfillment_policy/FULFILL-1",
        json={"fulfillmentPolicyId": "FULFILL-1"},
        status_code=200,
    )
    httpx_mock.add_response(
        method="DELETE",
        url="https://api.ebay.com/sell/account/v1/return_policy/RET-1",
        status_code=204,
    )

    client = AccountClient(build_transport())
    payment_payload = PaymentPolicyRequest(name="Default payment")
    fulfillment_payload = FulfillmentPolicyRequest(name="Fast shipping")

    created_payment = client.create_payment_policy(payment_payload)
    updated_fulfillment = client.update_fulfillment_policy("FULFILL-1", fulfillment_payload)
    client.delete_return_policy("RET-1")

    assert created_payment.paymentPolicyId == "PAY-NEW"
    assert updated_fulfillment.fulfillmentPolicyId == "FULFILL-1"

    create_request = httpx_mock.get_requests()[0]
    create_body = json.loads(create_request.content.decode("utf-8"))
    assert create_body["name"] == "Default payment"

    update_request = httpx_mock.get_requests()[1]
    update_body = json.loads(update_request.content.decode("utf-8"))
    assert update_body["name"] == "Fast shipping"


def test_account_wrapper_supports_return_policy_create_and_update(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/sell/account/v1/return_policy",
        json={"returnPolicyId": "RET-NEW"},
        status_code=201,
    )
    httpx_mock.add_response(
        method="PUT",
        url="https://api.ebay.com/sell/account/v1/return_policy/RET-NEW",
        json={"returnPolicyId": "RET-NEW"},
        status_code=200,
    )

    client = AccountClient(build_transport())
    payload = ReturnPolicyRequest(name="30 day returns")

    created = client.create_return_policy(payload)
    updated = client.update_return_policy("RET-NEW", payload)

    assert created.returnPolicyId == "RET-NEW"
    assert updated.returnPolicyId == "RET-NEW"


def test_feed_wrapper_returns_task_collection_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
        json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
    )

    client = FeedClient(build_transport())
    result = client.get_tasks(feed_type="LMS_ORDER_REPORT")

    assert isinstance(result, TaskCollection)
    assert result.total == 1
    assert result.tasks and result.tasks[0].taskId == "TASK-1"


def test_feed_wrapper_supports_schedule_crud_and_template_lookup(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/sell/feed/v1/schedule",
        status_code=201,
        headers={"Location": "https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1"},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
        json={"scheduleId": "SCHEDULE-1", "feedType": "LMS_ORDER_REPORT"},
    )
    httpx_mock.add_response(
        method="PUT",
        url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
        status_code=204,
    )
    httpx_mock.add_response(
        method="DELETE",
        url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
        status_code=204,
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/schedule_template/TEMPLATE-1",
        json={"scheduleTemplateId": "TEMPLATE-1", "feedType": "LMS_ORDER_REPORT"},
    )

    client = FeedClient(build_transport())
    created = client.create_schedule(
        CreateUserScheduleRequest(feedType="LMS_ORDER_REPORT", scheduleTemplateId="TEMPLATE-1")
    )
    schedule = client.get_schedule("SCHEDULE-1")
    client.update_schedule("SCHEDULE-1", UpdateUserScheduleRequest(scheduleName="nightly"))
    client.delete_schedule("SCHEDULE-1")
    template = client.get_schedule_template("TEMPLATE-1")

    assert isinstance(created, CreatedFeedResource)
    assert created.resource_id == "SCHEDULE-1"
    assert isinstance(schedule, UserScheduleResponse)
    assert schedule.scheduleId == "SCHEDULE-1"
    assert isinstance(template, ScheduleTemplateResponse)
    assert template.scheduleTemplateId == "TEMPLATE-1"

    create_body = json.loads(httpx_mock.get_requests()[0].content.decode("utf-8"))
    assert create_body["feedType"] == "LMS_ORDER_REPORT"
    update_body = json.loads(httpx_mock.get_requests()[2].content.decode("utf-8"))
    assert update_body["scheduleName"] == "nightly"


def test_feed_wrapper_supports_task_create_upload_and_file_downloads(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/sell/feed/v1/task",
        status_code=202,
        headers={"Location": "https://api.ebay.com/sell/feed/v1/task/TASK-2"},
    )
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/sell/feed/v1/task/TASK-2/upload_file",
        status_code=200,
        json={},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/task/TASK-2/download_input_file",
        status_code=200,
        headers={"content-disposition": 'attachment; filename="input.xml"'},
        content=b"<input />",
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/task/TASK-2/download_result_file",
        status_code=200,
        headers={"content-disposition": 'attachment; filename="result.csv"'},
        content=b"id,name\n1,demo\n",
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-9/download_result_file",
        status_code=200,
        headers={"content-disposition": 'attachment; filename="latest.csv"'},
        content=b"id,name\n2,latest\n",
    )

    client = FeedClient(build_transport())
    created = client.create_task(
        CreateTaskRequest(feedType="LMS_ORDER_REPORT", schemaVersion="1.0"),
        marketplace_id="EBAY_US",
    )
    client.upload_file("TASK-2", file_name="input.xml", content=b"<input />", content_type="application/xml")
    input_file = client.get_input_file("TASK-2")
    result_file = client.get_result_file("TASK-2")
    latest_file = client.get_latest_result_file("SCHEDULE-9")

    assert isinstance(created, CreatedFeedResource)
    assert created.resource_id == "TASK-2"
    assert isinstance(input_file, FeedFileDownload)
    assert input_file.file_name == "input.xml"
    assert input_file.content == b"<input />"
    assert result_file.file_name == "result.csv"
    assert result_file.content.startswith(b"id,name")
    assert latest_file.file_name == "latest.csv"

    create_request = httpx_mock.get_requests()[0]
    assert create_request.headers["X-EBAY-C-MARKETPLACE-ID"] == "EBAY_US"
    create_body = json.loads(create_request.content.decode("utf-8"))
    assert create_body["feedType"] == "LMS_ORDER_REPORT"

    upload_request = httpx_mock.get_requests()[1]
    assert upload_request.headers["Content-Type"].startswith("multipart/form-data;")
    assert b"filename=\"input.xml\"" in upload_request.content


def test_feed_wrapper_passes_task_and_schedule_filters(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url=(
            "https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT"
            "&schedule_id=SCHEDULE-1&date_range=2026-01-01T00:00:00.000Z..2026-01-02T00:00:00.000Z"
            "&look_back_days=7&limit=50&offset=0"
        ),
        json={"tasks": [], "total": 0},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/schedule?feed_type=LMS_ORDER_REPORT&limit=25&offset=0",
        json={"schedules": [], "total": 0},
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/schedule_template?feed_type=LMS_ORDER_REPORT&limit=25&offset=0",
        json={"scheduleTemplates": [], "total": 0},
    )

    client = FeedClient(build_transport())
    tasks = client.get_tasks(
        feed_type="LMS_ORDER_REPORT",
        schedule_id="SCHEDULE-1",
        date_range="2026-01-01T00:00:00.000Z..2026-01-02T00:00:00.000Z",
        look_back_days=7,
        limit=50,
        offset=0,
    )
    schedules = client.get_schedules(feed_type="LMS_ORDER_REPORT", limit=25, offset=0)
    templates = client.get_schedule_templates(feed_type="LMS_ORDER_REPORT", limit=25, offset=0)

    assert tasks.total == 0
    assert schedules.total == 0
    assert templates.total == 0


def test_inventory_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
    oauth_client = RecordingOAuthClient()
    transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
        json={"sku": "SKU-1"},
    )

    InventoryClient(transport).get_inventory_item("SKU-1")

    assert oauth_client.last_scopes is None
    assert oauth_client.last_scope_options == [
        ["https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"],
        ["https://api.ebay.com/oauth/api_scope/sell.inventory"],
    ]


def test_fulfillment_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
    oauth_client = RecordingOAuthClient()
    transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
        json={"orderId": "ORDER-1"},
    )

    FulfillmentClient(transport).get_order("ORDER-1")

    assert oauth_client.last_scopes is None
    assert oauth_client.last_scope_options == [
        ["https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"],
        ["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
    ]


def test_account_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
    oauth_client = RecordingOAuthClient()
    transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/account/v1/privilege",
        json={},
    )

    AccountClient(transport).get_privileges()

    assert oauth_client.last_scopes is None
    assert oauth_client.last_scope_options == [
        ["https://api.ebay.com/oauth/api_scope/sell.account.readonly"],
        ["https://api.ebay.com/oauth/api_scope/sell.account"],
    ]


def test_feed_wrapper_accepts_any_documented_feed_scope_option(httpx_mock: HTTPXMock) -> None:
    oauth_client = RecordingOAuthClient()
    transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
        json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
    )

    FeedClient(transport).get_tasks(feed_type="LMS_ORDER_REPORT")

    assert oauth_client.last_scopes is None
    assert oauth_client.last_scope_options == [
        ["https://api.ebay.com/oauth/api_scope/sell.inventory"],
        ["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
        ["https://api.ebay.com/oauth/api_scope/sell.marketing"],
        ["https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"],
        ["https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"],
    ]


def test_media_wrapper_returns_image_model_from_url(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/image/create_image_from_url",
        json={"imageUrl": "https://i.ebayimg.com/images/g/demo.jpg", "expirationDate": "2026-12-31T00:00:00Z"},
        status_code=201,
    )

    client = MediaClient(build_transport())
    result = client.create_image_from_url(
        CreateImageFromUrlRequest(imageUrl="https://example.test/demo.jpg")
    )

    assert isinstance(result, ImageResponse)
    assert result.imageUrl == "https://i.ebayimg.com/images/g/demo.jpg"
    request = httpx_mock.get_requests()[0]
    body = json.loads(request.content.decode("utf-8"))
    assert body["imageUrl"] == "https://example.test/demo.jpg"


def test_media_wrapper_serializes_multipart_image_upload(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/image/create_image_from_file",
        json={"imageUrl": "https://i.ebayimg.com/images/g/uploaded.jpg"},
        status_code=201,
    )

    client = MediaClient(build_transport())
    result = client.create_image_from_file(
        file_name="demo.jpg",
        content=b"binary-image-content",
        content_type="image/jpeg",
    )

    assert isinstance(result, ImageResponse)
    request = httpx_mock.get_requests()[0]
    assert request.headers["Content-Type"].startswith("multipart/form-data;")
    assert b"name=\"image\"" in request.content
    assert b"filename=\"demo.jpg\"" in request.content
    assert b"binary-image-content" in request.content


def test_media_wrapper_returns_created_resource_location_for_video(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/video",
        status_code=201,
        headers={"Location": "https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1"},
    )

    client = MediaClient(build_transport())
    result = client.create_video(
        CreateVideoRequest(title="Demo", size=1024, classification=["ITEM"])
    )

    assert isinstance(result, CreatedMediaResource)
    assert result.location == "https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1"
    assert result.resource_id == "VIDEO-1"


def test_extract_media_resource_id_handles_location_header() -> None:
    assert extract_resource_id("https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1") == "VIDEO-1"
    assert extract_resource_id("https://api.ebay.com/commerce/media/v1_beta/document/DOC-1/") == "DOC-1"
    assert extract_resource_id(None) is None


def test_media_wrapper_returns_video_model(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1",
        json={"videoId": "VIDEO-1", "status": "LIVE", "title": "Demo"},
    )

    client = MediaClient(build_transport())
    result = client.get_video("VIDEO-1")

    assert isinstance(result, Video)
    assert result.videoId == "VIDEO-1"


def test_media_wrapper_uploads_video_bytes(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1/upload",
        status_code=200,
    )

    client = MediaClient(build_transport())
    client.upload_video(
        "VIDEO-1",
        content=b"video-bytes",
        content_length=11,
        content_range="bytes 0-10/11",
    )

    request = httpx_mock.get_requests()[0]
    assert request.headers["Content-Type"] == "application/octet-stream"
    assert request.headers["Content-Length"] == "11"
    assert request.headers["Content-Range"] == "bytes 0-10/11"
    assert request.content == b"video-bytes"


def test_media_wrapper_returns_document_models(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/document",
        json={"documentId": "DOC-1", "documentStatus": "PENDING_UPLOAD"},
        status_code=201,
    )
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/document/create_document_from_url",
        json={"documentId": "DOC-2", "documentStatus": "SUBMITTED"},
        status_code=201,
    )
    httpx_mock.add_response(
        method="GET",
        url="https://api.ebay.com/commerce/media/v1_beta/document/DOC-1",
        json={"documentId": "DOC-1", "documentStatus": "ACCEPTED", "documentType": "USER_GUIDE_OR_MANUAL"},
    )
    httpx_mock.add_response(
        method="POST",
        url="https://api.ebay.com/commerce/media/v1_beta/document/DOC-1/upload",
        json={"documentId": "DOC-1", "documentStatus": "SUBMITTED"},
        status_code=200,
    )

    client = MediaClient(build_transport())
    created = client.create_document(
        CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"])
    )
    created_from_url = client.create_document_from_url(
        CreateDocumentFromUrlRequest(
            documentType="USER_GUIDE_OR_MANUAL",
            documentUrl="https://example.test/guide.pdf",
            languages=["en-US"],
        )
    )
    fetched = client.get_document("DOC-1")
    uploaded = client.upload_document(
        "DOC-1",
        file_name="guide.pdf",
        content=b"%PDF-1.7",
        content_type="application/pdf",
    )

    assert created.documentId == "DOC-1"
    assert created_from_url.documentId == "DOC-2"
    assert isinstance(fetched, DocumentResponse)
    assert fetched.documentStatus == "ACCEPTED"
    assert uploaded.documentStatus == "SUBMITTED"
    upload_request = httpx_mock.get_requests()[3]
    assert upload_request.headers["Content-Type"].startswith("multipart/form-data;")
    assert b"filename=\"guide.pdf\"" in upload_request.content
    assert b"%PDF-1.7" in upload_request.content


def test_media_wait_for_video_returns_live_payload(monkeypatch) -> None:
    client = MediaClient(build_transport())
    states = iter(
        [
            Video(videoId="VIDEO-1", status="PENDING_UPLOAD"),
            Video(videoId="VIDEO-1", status="PROCESSING"),
            Video(videoId="VIDEO-1", status="LIVE"),
        ]
    )

    monkeypatch.setattr(client, "get_video", lambda _video_id: next(states))
    monkeypatch.setattr("ebay_client.media.client.sleep", lambda _seconds: None)

    result = client.wait_for_video("VIDEO-1", poll_interval_seconds=0.0)

    assert result.status == "LIVE"


def test_media_wait_for_document_raises_on_terminal_failure(monkeypatch) -> None:
    client = MediaClient(build_transport())

    monkeypatch.setattr(
        client,
        "get_document",
        lambda _document_id: DocumentResponse(documentId="DOC-1", documentStatus="REJECTED"),
    )

    try:
        client.wait_for_document("DOC-1", poll_interval_seconds=0.0)
    except ValueError as exc:
        assert "REJECTED" in str(exc)
    else:
        raise AssertionError("Expected wait_for_document to raise on terminal failure status")


def test_media_create_upload_and_wait_video_orchestrates_flow(monkeypatch) -> None:
    client = MediaClient(build_transport())
    calls: list[tuple[str, object]] = []

    monkeypatch.setattr(
        client,
        "create_video",
        lambda payload: calls.append(("create_video", payload)) or CreatedMediaResource(resource_id="VIDEO-9"),
    )
    monkeypatch.setattr(
        client,
        "upload_video",
        lambda video_id, **kwargs: calls.append(("upload_video", {"video_id": video_id, **kwargs})),
    )
    monkeypatch.setattr(
        client,
        "wait_for_video",
        lambda video_id, **kwargs: calls.append(("wait_for_video", {"video_id": video_id, **kwargs}))
        or Video(videoId=video_id, status="LIVE"),
    )

    result = client.create_upload_and_wait_video(
        CreateVideoRequest(title="Demo", size=4, classification=["ITEM"]),
        content=b"demo",
        poll_interval_seconds=0.0,
    )

    assert isinstance(result, VideoWorkflowResult)
    assert result.video_id == "VIDEO-9"
    assert result.video.videoId == "VIDEO-9"
    assert result.created.resource_id == "VIDEO-9"
    assert calls[0][0] == "create_video"
    assert calls[1] == (
        "upload_video",
        {"video_id": "VIDEO-9", "content": b"demo", "content_length": 4, "content_range": None},
    )
    assert calls[2] == (
        "wait_for_video",
        {"video_id": "VIDEO-9", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
    )


def test_media_create_upload_and_wait_document_orchestrates_flow(monkeypatch) -> None:
    client = MediaClient(build_transport())
    calls: list[tuple[str, object]] = []

    monkeypatch.setattr(
        client,
        "create_document",
        lambda payload: calls.append(("create_document", payload))
        or CreateDocumentResponse(documentId="DOC-9", documentStatus="PENDING_UPLOAD"),
    )
    monkeypatch.setattr(
        client,
        "upload_document",
        lambda document_id, **kwargs: calls.append(("upload_document", {"document_id": document_id, **kwargs}))
        or DocumentResponse(documentId=document_id, documentStatus="SUBMITTED"),
    )
    monkeypatch.setattr(
        client,
        "wait_for_document",
        lambda document_id, **kwargs: calls.append(("wait_for_document", {"document_id": document_id, **kwargs}))
        or DocumentResponse(documentId=document_id, documentStatus="ACCEPTED"),
    )

    result = client.create_upload_and_wait_document(
        CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
        file_name="guide.pdf",
        content=b"%PDF-1.7",
        content_type="application/pdf",
        poll_interval_seconds=0.0,
    )

    assert isinstance(result, DocumentWorkflowResult)
    assert result.document_id == "DOC-9"
    assert result.created.documentId == "DOC-9"
    assert result.uploaded is not None and result.uploaded.documentStatus == "SUBMITTED"
    assert result.document.documentStatus == "ACCEPTED"
    assert calls[0][0] == "create_document"
    assert calls[1] == (
        "upload_document",
        {
            "document_id": "DOC-9",
            "file_name": "guide.pdf",
            "content": b"%PDF-1.7",
            "content_type": "application/pdf",
        },
    )
    assert calls[2] == (
        "wait_for_document",
        {"document_id": "DOC-9", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
    )


def test_media_create_document_from_url_and_wait_orchestrates_flow(monkeypatch) -> None:
    client = MediaClient(build_transport())
    calls: list[tuple[str, object]] = []

    monkeypatch.setattr(
        client,
        "create_document_from_url",
        lambda payload: calls.append(("create_document_from_url", payload))
        or CreateDocumentResponse(documentId="DOC-10", documentStatus="SUBMITTED"),
    )
    monkeypatch.setattr(
        client,
        "wait_for_document",
        lambda document_id, **kwargs: calls.append(("wait_for_document", {"document_id": document_id, **kwargs}))
        or DocumentResponse(documentId=document_id, documentStatus="ACCEPTED"),
    )

    result = client.create_document_from_url_and_wait(
        CreateDocumentFromUrlRequest(
            documentType="USER_GUIDE_OR_MANUAL",
            documentUrl="https://example.test/guide.pdf",
            languages=["en-US"],
        ),
        poll_interval_seconds=0.0,
    )

    assert isinstance(result, DocumentWorkflowResult)
    assert result.document_id == "DOC-10"
    assert result.created.documentId == "DOC-10"
    assert result.uploaded is None
    assert result.document.documentStatus == "ACCEPTED"
    assert calls[0][0] == "create_document_from_url"
    assert calls[1] == (
        "wait_for_document",
        {"document_id": "DOC-10", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
    )


def test_media_convenience_methods_raise_when_required_ids_are_missing(monkeypatch) -> None:
    client = MediaClient(build_transport())

    monkeypatch.setattr(client, "create_video", lambda payload: CreatedMediaResource(resource_id=None))
    monkeypatch.setattr(client, "create_document", lambda payload: CreateDocumentResponse(documentId=None))
    monkeypatch.setattr(client, "create_document_from_url", lambda payload: CreateDocumentResponse(documentId=None))

    for action in (
        lambda: client.create_upload_and_wait_video(
            CreateVideoRequest(title="Demo", size=4, classification=["ITEM"]),
            content=b"demo",
            poll_interval_seconds=0.0,
        ),
        lambda: client.create_upload_and_wait_document(
            CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
            file_name="guide.pdf",
            content=b"%PDF-1.7",
            poll_interval_seconds=0.0,
        ),
        lambda: client.create_document_from_url_and_wait(
            CreateDocumentFromUrlRequest(
                documentType="USER_GUIDE_OR_MANUAL",
                documentUrl="https://example.test/guide.pdf",
                languages=["en-US"],
            ),
            poll_interval_seconds=0.0,
        ),
    ):
        try:
            action()
        except RuntimeError:
            pass
        else:
            raise AssertionError("Expected convenience method to raise when eBay omits the required identifier")


def test_guess_media_content_type_uses_filename_extension() -> None:
    assert guess_media_content_type("photo.jpg") == "image/jpeg"
    assert guess_media_content_type("guide.pdf") == "application/pdf"
    assert guess_media_content_type("unknown.custom") == "application/octet-stream"


def test_media_create_image_from_path_reads_file_and_infers_content_type(tmp_path, monkeypatch) -> None:
    client = MediaClient(build_transport())
    image_path = tmp_path / "photo.png"
    image_path.write_bytes(b"png-data")

    captured: dict[str, object] = {}

    monkeypatch.setattr(
        client,
        "create_image_from_file",
        lambda **kwargs: captured.update(kwargs) or ImageResponse(imageUrl="https://example.test/image"),
    )

    result = client.create_image_from_path(image_path)

    assert result.imageUrl == "https://example.test/image"
    assert captured == {
        "file_name": "photo.png",
        "content": b"png-data",
        "content_type": "image/png",
    }


def test_media_upload_document_from_path_reads_file_and_infers_content_type(tmp_path, monkeypatch) -> None:
    client = MediaClient(build_transport())
    document_path = tmp_path / "guide.pdf"
    document_path.write_bytes(b"%PDF-1.7")

    captured: dict[str, object] = {}

    monkeypatch.setattr(
        client,
        "upload_document",
        lambda document_id, **kwargs: captured.update({"document_id": document_id, **kwargs})
        or DocumentResponse(documentId=document_id, documentStatus="SUBMITTED"),
    )

    result = client.upload_document_from_path("DOC-42", document_path)

    assert result.documentId == "DOC-42"
    assert captured == {
        "document_id": "DOC-42",
        "file_name": "guide.pdf",
        "content": b"%PDF-1.7",
        "content_type": "application/pdf",
    }


def test_media_create_upload_and_wait_document_from_path_reads_file_and_delegates(tmp_path, monkeypatch) -> None:
    client = MediaClient(build_transport())
    document_path = tmp_path / "guide.pdf"
    document_path.write_bytes(b"%PDF-1.7")

    captured: dict[str, object] = {}

    monkeypatch.setattr(
        client,
        "create_upload_and_wait_document",
        lambda payload, **kwargs: captured.update({"payload": payload, **kwargs})
        or DocumentWorkflowResult(
            created=CreateDocumentResponse(documentId="DOC-77", documentStatus="PENDING_UPLOAD"),
            uploaded=DocumentResponse(documentId="DOC-77", documentStatus="SUBMITTED"),
            document=DocumentResponse(documentId="DOC-77", documentStatus="ACCEPTED"),
            document_id="DOC-77",
        ),
    )

    result = client.create_upload_and_wait_document_from_path(
        CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
        document_path,
        poll_interval_seconds=0.0,
    )

    assert isinstance(result, DocumentWorkflowResult)
    assert result.document.documentStatus == "ACCEPTED"
    assert captured["file_name"] == "guide.pdf"
    assert captured["content"] == b"%PDF-1.7"
    assert captured["content_type"] == "application/pdf"
    assert captured["poll_interval_seconds"] == 0.0


def test_media_create_upload_and_wait_video_from_path_builds_payload_and_delegates(tmp_path, monkeypatch) -> None:
    client = MediaClient(build_transport())
    video_path = tmp_path / "demo.mp4"
    video_path.write_bytes(b"video-data")

    captured: dict[str, object] = {}

    monkeypatch.setattr(
        client,
        "create_upload_and_wait_video",
        lambda payload, **kwargs: captured.update({"payload": payload, **kwargs})
        or VideoWorkflowResult(
            created=CreatedMediaResource(resource_id="VIDEO-88"),
            video=Video(videoId="VIDEO-88", status="LIVE"),
            video_id="VIDEO-88",
        ),
    )

    result = client.create_upload_and_wait_video_from_path(
        video_path,
        description="demo video",
        poll_interval_seconds=0.0,
    )

    assert isinstance(result, VideoWorkflowResult)
    assert result.video.videoId == "VIDEO-88"
    payload = captured["payload"]
    assert isinstance(payload, CreateVideoRequest)
    assert payload.title == "demo"
    assert payload.size == len(b"video-data")
    assert payload.classification == ["ITEM"]
    assert payload.description == "demo video"
    assert captured["content"] == b"video-data"
    assert captured["poll_interval_seconds"] == 0.0