import base64
from pprint import pformat
from typing import ClassVar, override

from loguru import logger
from royal_mail_combined import RoyalMailClient
from royal_mail_combined.click_and_drop_api.models import (
    CreateOrderRequest,
    CreateOrdersRequest,
    CreateOrdersResponse,
)
from royal_mail_combined.click_and_drop_api.models.return_models import ReturnRequestContainer, ReturnResponseContainer
from royal_mail_combined.config import RoyalMailSettingsGlobal
from royal_mail_combined.converters_no_import import tracking_link
from royal_mail_combined.core.consts_types import RoyalMailServiceCodes

from shipaw.fapi.requests import ShipmentRequest
from shipaw.fapi.responses import CompletedShipmentResponse, ShipmentResponse
from shipaw.utils.consts_enums import PackageFormat, ShipDirection
from shipaw.models.shipment import Shipment
from shipaw.providers.provider_abc import ProviderName, ShippingProvider
from shipaw.providers.registry import register_provider_type
from shipaw.providers.royal_mail.royal_mail_funcs import (
    full_contact_from_rm,
    inbound_shipment_from_agnostic,
    outbound_shipment_from_agnostic,
)

from shipaw.utils.label_file import merge_pdf_bytes


def build_booking_response_inbound(rm_response: ReturnResponseContainer, shipment: Shipment, label_data: bytes):
    return CompletedShipmentResponse(
        label_data=label_data,
        shipment=shipment,
        shipment_num=rm_response.unique_ids_str,
        shipment_numbers=rm_response.unique_ids,
        tracking_links=rm_response.tracking_links,
        collection_id=rm_response.collection_response.collection_order_id,
        data=rm_response.model_dump(),
        status='Success',
        success=True,
    )


def print_response_errors(rm_response: CreateOrdersResponse):
    logger.error(f'Errors booking outbound shipment: {pformat(rm_response.failed_orders, indent=2, width=120)}')


def build_booking_response_outbound(
    rm_response: CreateOrdersResponse, shipment: Shipment, label_data: bytes
) -> CompletedShipmentResponse:
    success = rm_response.errors_count == 0
    tracking_numbers = [order.tracking_number for order in rm_response.created_orders]
    tracking_links = [tracking_link(_) for _ in tracking_numbers]
    res = CompletedShipmentResponse(
        shipment=shipment,
        label_data=label_data,
        shipment_num=rm_response.success_idents_str,
        shipment_numbers=rm_response.success_ident_strs,
        tracking_links=tracking_links,
        data={_.order_identifier: _.model_dump() for _ in rm_response.created_orders},
        status='Success' if success else 'FAIL',
        success=success,
    )
    return res


@register_provider_type
class RoyalMailProvider(ShippingProvider):
    settings: RoyalMailSettingsGlobal

    name: ClassVar[ProviderName] = ProviderName.ROYAL_MAIL
    settings_type: ClassVar[type[RoyalMailSettingsGlobal]] = RoyalMailSettingsGlobal
    service_codes_type: ClassVar[type[RoyalMailServiceCodes]] = RoyalMailServiceCodes
    default_service: ClassVar[RoyalMailServiceCodes] = RoyalMailServiceCodes.EXPRESS_24
    valid_directions: ClassVar[list[ShipDirection]] = [
        ShipDirection.OUTBOUND,
        ShipDirection.INBOUND,
        ShipDirection.DROPOFF,
    ]

    valid_direction_services = {
        ShipDirection.OUTBOUND: [RoyalMailServiceCodes.TRACKED_24, RoyalMailServiceCodes.EXPRESS_24],
        ShipDirection.INBOUND: [RoyalMailServiceCodes.TRACKED_24_RTN],
        ShipDirection.DROPOFF: [RoyalMailServiceCodes.TRACKED_24_RTN],
    }

    valid_direction_formats = {
        ShipDirection.OUTBOUND: [PackageFormat.PARCEL, PackageFormat.SMALL_PARCEL],
        ShipDirection.INBOUND: [PackageFormat.PARCEL],
        ShipDirection.DROPOFF: [PackageFormat.PARCEL],
    }

    _client: RoyalMailClient | None = None

    @override
    def is_sandbox(self) -> bool:
        # Royal mail do not have a test environment. that's fun
        return False

    @property
    @override
    def client(self) -> RoyalMailClient:
        if self._client is None:
            if self.settings is None:
                raise ValueError('Settings must be set before using the client')
            self._client = RoyalMailClient(settings=self.settings)
        return self._client

    @classmethod
    @override
    def agnostic_shipment(cls, orders_req: CreateOrdersRequest) -> Shipment:
        order = orders_req.items[0]
        return Shipment(
            recipient=full_contact_from_rm(order.recipient),
            boxes=len(order.packages),
            shipping_date=order.planned_despatch_date.date(),
            direction=ShipDirection.OUTBOUND,
            reference=order.order_reference,
        )

    @override
    def provider_shipment(
        self, shipment: Shipment, service_code: RoyalMailServiceCodes
    ) -> CreateOrderRequest | ReturnRequestContainer:
        provider_service = self.service_codes_type(service_code)
        match shipment.direction:
            case ShipDirection.OUTBOUND:
                return outbound_shipment_from_agnostic(shipment, provider_service)
            case ShipDirection.DROPOFF | ShipDirection.INBOUND:
                return inbound_shipment_from_agnostic(shipment, provider_service)

    @override
    def book_shipment_request(self, shipment_request: ShipmentRequest) -> ShipmentResponse:
        shipment = shipment_request.shipment
        service = self.service_codes_type(shipment_request.service_code)
        shipdir = shipment_request.shipment.direction
        if shipdir == ShipDirection.OUTBOUND:
            return self.book_outbound(service, shipment)
        elif shipdir in [ShipDirection.INBOUND, ShipDirection.DROPOFF]:
            return self.book_inbound_or_dropoff(service, shipment)
        else:
            raise ValueError(f'Invalid shipment direction "{shipdir}"')

    def book_inbound_or_dropoff(self, service: RoyalMailServiceCodes, shipment: Shipment) -> CompletedShipmentResponse:
        returns_container = inbound_shipment_from_agnostic(shipment, service)
        if shipment.direction == ShipDirection.INBOUND:
            resp = self.client.book_inbound_collection(returns_container, collection_date=shipment.shipping_date)
        else:
            resp = self.client.book_inbound_dropoff(returns_container)
        labels_bytes = [base64.b64decode(order.label) for order in resp.created_orders]
        combined_label_bytes = merge_pdf_bytes(labels_bytes)
        return build_booking_response_inbound(resp, shipment, combined_label_bytes)

    def book_outbound(self, service: RoyalMailServiceCodes, shipment: Shipment) -> CompletedShipmentResponse:
        order_create = outbound_shipment_from_agnostic(shipment, service)
        resp = self.client.book_outbound(order_create)
        label_data: bytearray = self.client.get_label_data(resp.success_idents_str)
        if resp.errors_count > 0:
            print_response_errors(resp)
        return build_booking_response_outbound(resp, shipment, label_data)

    @override
    def fetch_label_content(self, shipment_num: str) -> bytes:
        return self.client.get_label_data(shipment_num)

    # def build_booking_response_outbound(self, rm_response: CreateOrdersResponse, shipment: Shipment):
    #     """Builds a ShipmentResponse for an outbound Royal Mail shipment booking.
    #     These will not have label data as it is not possible to request multiple labels in one go while booking,
    #     so we have to fetch them here after booking."""
    #
    #     if rm_response.errors_count > 0:
    #         print_response_errors(rm_response)
    #
    #     label_data: bytearray = self.client.get_label_data(rm_response.success_idents_str)
    #     fetched = self.client.fetch_specific_orders(rm_response.success_idents_str)
    #
    #     success = rm_response.errors_count == 0
    #     tracking_numbers = [order.tracking_number for order in fetched]
    #     tracking_links = [tracking_link(_) for _ in tracking_numbers]
    #     res = ShipmentResponse(
    #         shipment=shipment,
    #         label_data=label_data,
    #         shipment_num=rm_response.success_idents_str,
    #         shipment_numbers=rm_response.success_ident_strs,
    #         tracking_links=tracking_links,
    #         data={_.order_identifier: _ for _ in fetched},
    #         status='Success' if success else 'FAIL',
    #         success=success,
    #     )
    #     return res
