from __future__ import annotations

import warnings
from dataclasses import asdict
from typing import Any, Dict, Iterator, Optional, Union, TYPE_CHECKING

from ..types import CreateAssetParams, FilterSyncResult, ListAssetsParams, UpdateAssetParams, VerificationResult

if TYPE_CHECKING:
    from ..client import Optropic


def _build_create_payload(params: Union[CreateAssetParams, Dict[str, Any]]) -> Dict[str, Any]:
    """Build a Gateway-compatible payload from SDK params.

    Merges ``product_name`` into ``config`` so the Gateway stores it
    in ``asset_config``.  Strips None values.
    """
    if isinstance(params, CreateAssetParams):
        raw = asdict(params)
    else:
        raw = dict(params)

    config = dict(raw.pop("config", None) or {})
    product_name = raw.pop("product_name", None)
    if product_name is not None and "product_name" not in config:
        warnings.warn(
            "product_name as top-level field is deprecated. "
            "Pass it inside config={'product_name': ...} instead.",
            DeprecationWarning,
            stacklevel=3,
        )
        config["product_name"] = product_name

    payload: Dict[str, Any] = {}
    for key in ("external_id", "vertical", "security_level", "metadata"):
        val = raw.get(key)
        if val is not None:
            payload[key] = val

    if config:
        payload["config"] = config

    return payload


def _build_update_payload(params: Union[UpdateAssetParams, Dict[str, Any]]) -> Dict[str, Any]:
    """Build a Gateway-compatible payload for asset updates."""
    if isinstance(params, UpdateAssetParams):
        raw = asdict(params)
    else:
        raw = dict(params)

    config = dict(raw.pop("config", None) or {})
    product_name = raw.pop("product_name", None)
    if product_name is not None and "product_name" not in config:
        warnings.warn(
            "product_name as top-level field is deprecated. "
            "Pass it inside config={'product_name': ...} instead.",
            DeprecationWarning,
            stacklevel=3,
        )
        config["product_name"] = product_name

    payload: Dict[str, Any] = {}
    for key in ("external_id", "vertical", "metadata"):
        val = raw.get(key)
        if val is not None:
            payload[key] = val

    if config:
        payload["config"] = config

    return payload


class Assets:
    """Operations on digital assets."""

    def __init__(self, client: Optropic) -> None:
        self._client = client

    def create(
        self,
        params: Optional[CreateAssetParams] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Create a new asset and return it together with its cryptographic seal.

        Accepts either a :class:`CreateAssetParams` instance or keyword
        arguments::

            # dataclass style (backward compatible)
            client.assets.create(CreateAssetParams(product_name="Widget"))

            # keyword style (v1.0.2+)
            client.assets.create(
                external_id="SKU-001",
                config={"product_name": "Widget"},
            )
        """
        if params is None:
            params = kwargs
        payload = _build_create_payload(params)
        return self._client._request("POST", "/assets", json=payload)

    def list(self, params: Optional[ListAssetsParams] = None) -> Dict[str, Any]:
        """List assets with optional filtering and pagination.

        When the client uses a sandbox/test API key the ``is_sandbox``
        filter is applied automatically so that sandbox clients only see
        sandbox assets.  Pass an explicit ``is_sandbox`` value in *params*
        to override this behaviour.
        """
        query: Optional[Dict[str, Any]] = asdict(params) if params else None

        # Auto-filter sandbox assets when the client is in sandbox mode
        # and the caller didn't provide an explicit is_sandbox value.
        if self._client.is_sandbox:
            if query is None:
                query = {"is_sandbox": True}
            elif query.get("is_sandbox") is None:
                query["is_sandbox"] = True

        return self._client._request("GET", "/assets", params=query)

    def get(self, asset_id: str) -> Dict[str, Any]:
        """Retrieve a single asset by ID."""
        return self._client._request("GET", f"/assets/{asset_id}")

    def update(
        self,
        asset_id: str,
        params: Optional[UpdateAssetParams] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Update an existing asset."""
        if params is None:
            params = kwargs
        payload = _build_update_payload(params)
        return self._client._request("PATCH", f"/assets/{asset_id}", json=payload)

    def revoke(self, asset_id: str, reason: str) -> Dict[str, Any]:
        """Revoke an asset, providing a reason for the revocation."""
        return self._client._request(
            "POST",
            f"/assets/{asset_id}/revoke",
            json={"reason": reason},
        )

    def verify(
        self,
        asset_id: str,
        *,
        local_filter: Optional[bytes] = None,
        filter_salts: Optional[Dict[str, bytes]] = None,
        public_key: Optional[bytes] = None,
        filter_policy: str = "permissive",
        trust_window_seconds: int = 259200,
    ) -> VerificationResult:
        """Verify an asset's cryptographic seal and revocation status.

        Supports both online (API call) and offline (local Cuckoo filter) verification.
        When a local_filter is provided, performs offline verification without network access.
        """
        if local_filter is not None:
            from ..filter_verify import verify_offline
            return verify_offline(
                asset_id,
                local_filter,
                filter_salts or {},
                public_key=public_key,
                filter_policy=filter_policy,
                trust_window_seconds=trust_window_seconds,
            )

        data = self._client._request("POST", f"/assets/{asset_id}/verify")
        return VerificationResult(
            signature_valid=data["signature_valid"],
            revocation_status=data["revocation_status"],
            filter_age_seconds=data["filter_age_seconds"],
            verified_at=data["verified_at"],
            verification_mode=data["verification_mode"],
        )

    def list_all(
        self,
        *,
        status: Optional[str] = None,
        vertical: Optional[str] = None,
        is_sandbox: Optional[bool] = None,
        page_size: int = 100,
    ) -> Iterator[Dict[str, Any]]:
        """Auto-paginate through all assets, yielding individual items.

        Example::

            for asset in client.assets.list_all(status="active"):
                print(asset["id"])
        """
        params: Dict[str, Any] = {}
        if status is not None:
            params["status"] = status
        if vertical is not None:
            params["vertical"] = vertical
        if is_sandbox is not None:
            params["is_sandbox"] = is_sandbox
        elif self._client.is_sandbox:
            params["is_sandbox"] = True

        yield from self._client._paginate(
            "GET", "/assets", params=params, page_size=page_size,
        )

    def sync_filter(self) -> FilterSyncResult:
        """Download the latest revocation filter for offline use.

        Returns the signed binary filter and per-tenant salts.
        Call this periodically (e.g. every 6 hours) to keep the filter fresh.
        """
        from ..filter_verify import parse_header, parse_salts_header
        from datetime import datetime, timezone

        response = self._client._request_raw("GET", "/sync/revocation-filter")
        filter_bytes = response.content

        salts_header = response.headers.get("x-optropic-filter-salts", "")
        salt_header = response.headers.get("x-optropic-filter-salt", "")

        if salts_header:
            salts = parse_salts_header(salts_header)
        elif salt_header:
            salts = {"_self": bytes.fromhex(salt_header)}
        else:
            salts = {}

        header = parse_header(filter_bytes)

        return FilterSyncResult(
            filter=filter_bytes,
            salts=salts,
            issued_at=datetime.fromtimestamp(header["issued_at"], tz=timezone.utc),
            item_count=header["item_count"],
        )
