"""Exports resource for data export operations."""

from __future__ import annotations

from collections.abc import AsyncIterator, Iterator
from datetime import datetime
from typing import TYPE_CHECKING

from ticksupply._base import datetime_to_nanoseconds
from ticksupply.models.export import (
    ExportDownloadResponse,
    ExportJob,
    PaginatedExportJobs,
)
from ticksupply.resources.base import AsyncResource, SyncResource

if TYPE_CHECKING:
    from ticksupply._base import AsyncHTTPClient, SyncHTTPClient


class ExportsResource(SyncResource):
    """Synchronous resource for export operations.

    Exports allow you to download historical market data for a specific
    time range from your subscriptions.
    """

    def __init__(self, client: SyncHTTPClient) -> None:
        super().__init__(client)

    def list(
        self,
        *,
        limit: int | None = None,
        page_token: str | None = None,
    ) -> PaginatedExportJobs:
        """List export jobs for the authenticated account.

        Args:
            limit: Maximum number of export jobs to return (default 50, max 100).
            page_token: Token for pagination (from previous response).

        Returns:
            Paginated list of export jobs.

        Example:
            >>> page = client.exports.list(limit=10)
            >>> for job in page.items:
            ...     print(f"{job.id}: {job.status}")
        """
        response = self._client.get(
            "/exports",
            params={"limit": limit, "page_token": page_token},
        )
        return PaginatedExportJobs.model_validate(response.json())

    def list_all(self, *, limit: int | None = None) -> Iterator[ExportJob]:
        """Iterate over all export jobs with automatic pagination.

        Args:
            limit: Page size for each request (default 50, max 100).

        Yields:
            ExportJob objects.

        Example:
            >>> for job in client.exports.list_all():
            ...     print(f"{job.id}: {job.status}")
        """
        page_token: str | None = None
        while True:
            page = self.list(limit=limit, page_token=page_token)
            yield from page.items
            if not page.has_next:
                break
            page_token = page.next_page_token

    def get(self, export_id: str) -> ExportJob:
        """Get an export job by ID.

        Args:
            export_id: UUID of the export job.

        Returns:
            Export job details.

        Example:
            >>> job = client.exports.get("123e4567-e89b-12d3-a456-426614174000")
            >>> print(f"Status: {job.status}")
        """
        response = self._client.get(f"/exports/{export_id}")
        return ExportJob.model_validate(response.json())

    def create(
        self,
        datastream_id: int,
        start_time: datetime,
        end_time: datetime,
    ) -> ExportJob:
        """Create a new export job.

        You must have an active or paused subscription to the datastream to create
        an export. The export will only include data from periods when your
        subscription was active.

        Args:
            datastream_id: ID of the datastream to export.
            start_time: Start of the time range to export. If naive, assumed to be UTC.
            end_time: End of the time range to export. If naive, assumed to be UTC.

        Returns:
            Created export job details.

        Example:
            >>> from datetime import datetime, timezone
            >>>
            >>> # Get the datastream_id for your subscription
            >>> datastreams = client.exchanges.get_datastreams("binance", "BTCUSDT", "trades")
            >>> datastream_id = datastreams[0].datastream_id
            >>>
            >>> # Create an export for the last 24 hours
            >>> end = datetime.now(timezone.utc)
            >>> start = end - timedelta(hours=24)
            >>> job = client.exports.create(datastream_id, start, end)
            >>> print(f"Export job created: {job.id}")
        """
        # Convert datetime to nanoseconds
        start_ns = datetime_to_nanoseconds(start_time)
        end_ns = datetime_to_nanoseconds(end_time)

        payload = {
            "datastream_id": datastream_id,
            "start_time": str(start_ns),
            "end_time": str(end_ns),
        }

        response = self._client.post("/exports", json_data=payload)
        return ExportJob.model_validate(response.json())

    def get_download(self, export_id: str) -> ExportDownloadResponse:
        """Get pre-signed download URLs for a completed export.

        The export must have status "succeeded" to get download URLs.
        URLs are valid for 5 minutes.

        Large exports may be split into multiple files (hourly or daily
        partitions). Use the ``artifacts`` list to download all files.

        Args:
            export_id: UUID of the export job.

        Returns:
            Download response with one or more artifact URLs.

        Example:
            >>> download = client.exports.get_download("123e4567-e89b-12d3-a456-426614174000")
            >>> print(f"Total files: {download.count}, Total bytes: {download.total_bytes}")
            >>> for artifact in download.artifacts:
            ...     print(f"  {artifact.filename}: {artifact.url}")
            >>> # For single-file exports, convenience property is available:
            >>> if download.is_single_file:
            ...     print(f"Download URL: {download.url}")
        """
        response = self._client.get(f"/exports/{export_id}/download")
        return ExportDownloadResponse.model_validate(response.json())

    def delete(self, export_id: str) -> None:
        """Delete an export job.

        Args:
            export_id: UUID of the export job to delete.

        Example:
            >>> client.exports.delete("123e4567-e89b-12d3-a456-426614174000")
        """
        self._client.delete(f"/exports/{export_id}")

    def cancel(self, export_id: str) -> None:
        """Cancel a queued or running export job.

        Only exports with status "queued" or "running" can be canceled.
        Already canceled exports will return successfully (idempotent).

        Args:
            export_id: UUID of the export job to cancel.

        Raises:
            APIError: If export is already completed (succeeded/failed/deleted).

        Example:
            >>> client.exports.cancel("123e4567-e89b-12d3-a456-426614174000")
        """
        self._client.post(f"/exports/{export_id}/cancel")


class AsyncExportsResource(AsyncResource):
    """Asynchronous resource for export operations.

    Exports allow you to download historical market data for a specific
    time range from your subscriptions.
    """

    def __init__(self, client: AsyncHTTPClient) -> None:
        super().__init__(client)

    async def list(
        self,
        *,
        limit: int | None = None,
        page_token: str | None = None,
    ) -> PaginatedExportJobs:
        """List export jobs for the authenticated account.

        Args:
            limit: Maximum number of export jobs to return (default 50, max 100).
            page_token: Token for pagination (from previous response).

        Returns:
            Paginated list of export jobs.
        """
        response = await self._client.get(
            "/exports",
            params={"limit": limit, "page_token": page_token},
        )
        return PaginatedExportJobs.model_validate(response.json())

    async def list_all(self, *, limit: int | None = None) -> AsyncIterator[ExportJob]:
        """Iterate over all export jobs with automatic pagination.

        Args:
            limit: Page size for each request (default 50, max 100).

        Yields:
            ExportJob objects.
        """
        page_token: str | None = None
        while True:
            page = await self.list(limit=limit, page_token=page_token)
            for item in page.items:
                yield item
            if not page.has_next:
                break
            page_token = page.next_page_token

    async def get(self, export_id: str) -> ExportJob:
        """Get an export job by ID.

        Args:
            export_id: UUID of the export job.

        Returns:
            Export job details.
        """
        response = await self._client.get(f"/exports/{export_id}")
        return ExportJob.model_validate(response.json())

    async def create(
        self,
        datastream_id: int,
        start_time: datetime,
        end_time: datetime,
    ) -> ExportJob:
        """Create a new export job.

        You must have an active or paused subscription to the datastream to create
        an export. The export will only include data from periods when your
        subscription was active.

        Args:
            datastream_id: ID of the datastream to export.
            start_time: Start of the time range to export. If naive, assumed to be UTC.
            end_time: End of the time range to export. If naive, assumed to be UTC.

        Returns:
            Created export job details.
        """
        # Convert datetime to nanoseconds
        start_ns = datetime_to_nanoseconds(start_time)
        end_ns = datetime_to_nanoseconds(end_time)

        payload = {
            "datastream_id": datastream_id,
            "start_time": str(start_ns),
            "end_time": str(end_ns),
        }

        response = await self._client.post("/exports", json_data=payload)
        return ExportJob.model_validate(response.json())

    async def get_download(self, export_id: str) -> ExportDownloadResponse:
        """Get pre-signed download URLs for a completed export.

        The export must have status "succeeded" to get download URLs.
        URLs are valid for 5 minutes.

        Large exports may be split into multiple files (hourly or daily
        partitions). Use the ``artifacts`` list to download all files.

        Args:
            export_id: UUID of the export job.

        Returns:
            Download response with one or more artifact URLs.
        """
        response = await self._client.get(f"/exports/{export_id}/download")
        return ExportDownloadResponse.model_validate(response.json())

    async def delete(self, export_id: str) -> None:
        """Delete an export job.

        Args:
            export_id: UUID of the export job to delete.
        """
        await self._client.delete(f"/exports/{export_id}")

    async def cancel(self, export_id: str) -> None:
        """Cancel a queued or running export job.

        Only exports with status "queued" or "running" can be canceled.
        Already canceled exports will return successfully (idempotent).

        Args:
            export_id: UUID of the export job to cancel.

        Raises:
            APIError: If export is already completed (succeeded/failed/deleted).
        """
        await self._client.post(f"/exports/{export_id}/cancel")
