from typing import NotRequired, TypedDict, Unpack, final

from pydantic import TypeAdapter

from ksef2.core import routes
from ksef2.endpoints.base import BaseEndpoints
from ksef2.infra.schema.api import spec
from ksef2.infra.schema.api.supp.invoices import (
    InvoiceExportRequest,
    SendInvoiceRequest,
)

InvoiceMetadataQueryParams = TypedDict(
    "InvoiceMetadataQueryParams",
    {
        "sortOrder": NotRequired[str | None],
        "pageOffset": NotRequired[int | None],
        "pageSize": NotRequired[int | None],
    },
)
SessionInvoiceListQueryParams = TypedDict(
    "SessionInvoiceListQueryParams",
    {
        "pageSize": int,
    },
)


@final
class InvoicesEndpoints(BaseEndpoints):
    """Raw invoice endpoints backed by generated schema models."""

    _METADATA_PARAMS = TypeAdapter(InvoiceMetadataQueryParams)

    def query_metadata(
        self,
        body: spec.InvoiceQueryFilters,
        **params: Unpack[InvoiceMetadataQueryParams],
    ) -> spec.QueryInvoicesMetadataResponse:
        """Fetch one page of invoice metadata."""
        return self._parse(
            self._transport.post(
                path=routes.InvoiceRoutes.QUERY_METADATA,
                params=self.build_params(params, self._METADATA_PARAMS),
                json=body.model_dump(mode="json", by_alias=True),
            ),
            spec.QueryInvoicesMetadataResponse,
        )

    _SESSION_LIST_PARAMS = TypeAdapter(SessionInvoiceListQueryParams)

    def list_session_invoices(
        self,
        reference_number: str,
        continuation_token: str | None = None,
        **params: Unpack[SessionInvoiceListQueryParams],
    ) -> spec.SessionInvoicesResponse:
        """Fetch one page of invoices submitted in a session."""
        headers = (
            {"x-continuation-token": continuation_token} if continuation_token else None
        )

        return self._parse(
            self._transport.get(
                path=routes.InvoiceRoutes.LIST_SESSION_INVOICES.format(
                    referenceNumber=reference_number
                ),
                params=self.build_params(params, self._SESSION_LIST_PARAMS),
                headers=headers,
            ),
            spec.SessionInvoicesResponse,
        )

    def list_failed_session_invoices(
        self,
        reference_number: str,
        continuation_token: str | None = None,
        **params: Unpack[SessionInvoiceListQueryParams],
    ) -> spec.SessionInvoicesResponse:
        """Fetch one page of failed invoices from a session."""
        headers = (
            {"x-continuation-token": continuation_token} if continuation_token else None
        )

        return self._parse(
            self._transport.get(
                path=routes.InvoiceRoutes.LIST_FAILED_SESSION_INVOICES.format(
                    referenceNumber=reference_number
                ),
                params=self.build_params(params, self._SESSION_LIST_PARAMS),
                headers=headers,
            ),
            spec.SessionInvoicesResponse,
        )

    def export(self, body: InvoiceExportRequest) -> spec.ExportInvoicesResponse:
        """Start an invoice export operation."""
        return self._parse(
            self._transport.post(
                path=routes.InvoiceRoutes.EXPORT,
                json=body.model_dump(mode="json", by_alias=True),
            ),
            spec.ExportInvoicesResponse,
        )

    def get_export_status(
        self, reference_number: str
    ) -> spec.InvoiceExportStatusResponse:
        """Fetch status for a scheduled invoice export."""
        return self._parse(
            self._transport.get(
                path=routes.InvoiceRoutes.EXPORT_STATUS.format(
                    referenceNumber=reference_number
                ),
            ),
            spec.InvoiceExportStatusResponse,
        )

    def download(self, ksef_number: str) -> bytes:
        """Download raw invoice bytes by KSeF number."""
        return self._transport.get(
            path=routes.InvoiceRoutes.DOWNLOAD.format(ksefNumber=ksef_number),
        ).content

    def send(
        self,
        reference_number: str,
        body: SendInvoiceRequest,
    ) -> spec.SendInvoiceResponse:
        """Send one encrypted invoice into an open session."""
        return self._parse(
            self._transport.post(
                path=routes.InvoiceRoutes.SEND.format(referenceNumber=reference_number),
                json=body.model_dump(mode="json", by_alias=True),
            ),
            spec.SendInvoiceResponse,
        )

    def get_session_status(self, reference_number: str) -> spec.SessionStatusResponse:
        """Fetch status for an online or batch session."""
        return self._parse(
            self._transport.get(
                path=routes.InvoiceRoutes.SESSION_STATUS.format(
                    referenceNumber=reference_number
                ),
            ),
            spec.SessionStatusResponse,
        )

    def get_session_invoice_status(
        self,
        reference_number: str,
        invoice_reference_number: str,
    ) -> spec.SessionInvoiceStatusResponse:
        """Fetch status for a single invoice inside a session."""
        return self._parse(
            self._transport.get(
                path=routes.InvoiceRoutes.SESSION_INVOICE_STATUS.format(
                    referenceNumber=reference_number,
                    invoiceReferenceNumber=invoice_reference_number,
                ),
            ),
            spec.SessionInvoiceStatusResponse,
        )

    def get_invoice_upo_by_ksef(
        self,
        reference_number: str,
        ksef_number: str,
    ) -> bytes:
        """Download a UPO document by KSeF number."""
        return self._transport.get(
            path=routes.InvoiceRoutes.INVOICE_UPO_BY_KSEF.format(
                referenceNumber=reference_number,
                ksefNumber=ksef_number,
            ),
        ).content

    def get_invoice_upo_by_reference(
        self,
        reference_number: str,
        invoice_reference_number: str,
    ) -> bytes:
        """Download a UPO document by session invoice reference number."""
        return self._transport.get(
            path=routes.InvoiceRoutes.INVOICE_UPO_BY_REFERENCE.format(
                referenceNumber=reference_number,
                invoiceReferenceNumber=invoice_reference_number,
            ),
        ).content
