import asyncio
from typing import Optional, List, Any, Sequence, Tuple, Union

import httpx
from dotenv import find_dotenv
from pydantic import ValidationError, Field
from pydantic_settings import BaseSettings, SettingsConfigDict

from common.logging import get_logger, span
from common.models.common import UserAccessToken, Platform, AppAccessToken
from common.platform.twitch.model import (
    User,
    Stream,
    EventSubSubscription,
    EventSubTransport,
    EventSubConduit,
    EventSubConduitShard,
    EventSubNotification,
    EventSubSubscriptionRequest,
    TwitchChatMessageRequest,
    TwitchChatMessageResponse,
    ChannelInfo,
    MarkStreamRequest,
    MarkStreamResponse,
    GetStreamMarkersResponse,
)

logger = get_logger(__name__)


class TwitchApiConfig(BaseSettings):
    """
    Configuration settings for the Twitch API.

    Attributes:
    -----------
    client_id: str
        Twitch client ID
    secret: str
        Twitch secret
    """

    twitch_client_id: str = Field(default="", description="Twitch client ID")
    twitch_secret: str = ""
    twitch_base_url: str = Field(default="https://api.twitch.tv/helix")

    model_config = SettingsConfigDict(
        env_file=find_dotenv(), env_file_encoding="utf-8", extra="ignore"
    )


class TwitchApi:
    """
    Twitch API client for interacting with the Twitch API.

    Attributes:
    -----------
    config: TwitchApiConfig
        Twitch API configuration settings

    Methods:
    --------
    authenticate():
        Authenticate with Twitch using Client Credentials Flow.
    ensure_token():
        Ensure that the access token is valid. Authenticate if necessary.
    get_headers():
        Generate headers for Twitch API requests.
    get_user(user_login: str):
        Get user information by username.
    get_streams(user_ids: Optional[List[str]], user_logins: Optional[List[str]]):
        Get streams based on user IDs or logins.
    create_eventsub_subscription(request: EventSubSubscriptionRequest):
        Create a new EventSub subscription.
    delete_eventsub_subscription(subscription_id: str):
        Delete an existing EventSub subscription.
    list_eventsub_subscriptions():
        List all EventSub subscriptions.
    get_eventsub_notification(payload: dict):
        Validate and parse an EventSub notification payload.
    create_eventsub_conduit(shard_count: int):
        Create a new EventSub Conduit with the specified number of shards.
    update_eventsub_conduit_shards(conduit_id: str, shards: List[EventSubTransport]):
        Assign transports to shards within a Conduit.
    list_eventsub_conduits():
        List all EventSub Conduits.
    get_conduit_shards(conduit_id: str):
        Get all shards for a specific EventSub Conduit.
    delete_eventsub_conduit(conduit_id: str):
        Delete an existing EventSub Conduit.
    subscribe_to_conduit_event(conduit_id: str, subscription_type: str, version: str, condition: dict):
        Create an EventSub subscription using a Conduit.
    close():
        Close the HTTP client session
    """

    platform = Platform.TWITCH

    def __init__(
        self,
        config: TwitchApiConfig,
        scopes: Optional[List[str]] = None,
        access_token: Optional[AppAccessToken] = None,
    ) -> None:
        self._base_url = config.twitch_base_url
        self._token_url = "https://id.twitch.tv/oauth2/token"
        self.access_token = access_token
        self.token_expiry: Optional[float] = None
        self.config = config
        self.client = httpx.AsyncClient(timeout=30.0)
        self.scopes = scopes or []
        logger.info("Twitch API client initialized.")

    async def authenticate(self):
        """
        Authenticate with Twitch using Client Credentials Flow.
        """
        with span(
            logger,
            "twitch_authenticate",
            {"scopes": self.scopes},
            log_entry=False,
            log_exit=False,
            low_level=True,
        ):
            data = {
                "client_id": self.config.twitch_client_id,
                "client_secret": self.config.twitch_secret,
                "grant_type": "client_credentials",
                "scope": " ".join(self.scopes),
            }

            try:
                response = await self.client.post(self._token_url, data=data)
                response.raise_for_status()
                token_data = AppAccessToken(**response.json())
                self.access_token = token_data
                self.token_expiry = (
                    asyncio.get_event_loop().time() + token_data.expires_in
                )
                logger.info(
                    f"Successfully authenticated with Twitch API, token expires in {token_data.expires_in} seconds"
                )
            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Authentication failed with status {e.response.status_code}: {e}"
                )
                raise
            except ValidationError as e:
                logger.error(f"Failed to parse authentication response: {e}")
                raise
            except httpx.HTTPError as e:
                logger.error(f"HTTP error during authentication: {e}")
                raise

    async def exchange_oauth_code(
        self, code: str, redirect_uri: str = "http://localhost:3000"
    ) -> Any:
        """
        Exchange the OAuth code for an access token.

        Args:
            code: The OAuth authorization code
            redirect_uri: The redirect URI used in the authorization request

        Returns:
            The token data from Twitch

        Raises:
            httpx.HTTPError: If the HTTP request fails
            ValidationError: If the response cannot be parsed
        """
        with span(logger, "exchange_oauth_code", {"redirect_uri": redirect_uri}):
            logger.info(
                f"Exchanging OAuth code for access token with redirect URI: {redirect_uri}"
            )
            data = {
                "client_id": self.config.twitch_client_id,
                "client_secret": self.config.twitch_secret,
                "code": code,
                "grant_type": "authorization_code",
                "redirect_uri": redirect_uri,
            }
            try:
                response = await self.client.post(self._token_url, data=data)
                response.raise_for_status()
                token_data = response.json()
                logger.info("OAuth code exchanged successfully for access token")
                return token_data
            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Failed to exchange OAuth code with status {e.response.status_code}: {e}"
                )
                raise
            except httpx.HTTPError as e:
                logger.error(f"HTTP error during OAuth code exchange: {e}")
                raise
            except Exception as e:
                logger.error(f"Unexpected error during OAuth code exchange: {e}")
                raise

    async def get_user_access_token(
        self, code: str, redirect_uri: str = "http://localhost:3000"
    ) -> UserAccessToken:
        """
        Get a user access token by exchanging an OAuth code.

        Args:
            code: The OAuth authorization code
            redirect_uri: The redirect URI used in the authorization request

        Returns:
            UserAccessToken: The user access token from Twitch

        Raises:
            httpx.HTTPError: If the HTTP request fails
            ValidationError: If the response cannot be parsed
        """
        with span(logger, "get_user_access_token", {"redirect_uri": redirect_uri}):
            logger.info(f"Getting user access token with redirect URI: {redirect_uri}")
            data = {
                "client_id": self.config.twitch_client_id,
                "client_secret": self.config.twitch_secret,
                "code": code,
                "grant_type": "authorization_code",
                "redirect_uri": redirect_uri,
            }
            try:
                response = await self.client.post(
                    "https://id.twitch.tv/oauth2/token", data=data
                )
                response.raise_for_status()
                token_data = response.json()
                access_token = UserAccessToken(**token_data)
                logger.info(
                    f"User access token obtained successfully, expires in {access_token.expires_in} seconds"
                )
                return access_token
            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Failed to get user access token with status {e.response.status_code}: {e}"
                )
                raise
            except ValidationError as e:
                logger.error(f"Failed to parse user access token response: {e}")
                raise
            except httpx.HTTPError as e:
                logger.error(f"HTTP error during user access token request: {e}")
                raise
            except Exception as e:
                logger.error(f"Unexpected error getting user access token: {e}")
                raise

    async def refresh_user_token(self, refresh_token: str) -> dict:
        """Refresh an expired user access token using the refresh token.

        Args:
            refresh_token: The refresh token from the user's access token

        Returns:
            dict: The new token data from Twitch

        Raises:
            httpx.HTTPError: If the HTTP request fails
        """
        data = {
            "client_id": self.config.twitch_client_id,
            "client_secret": self.config.twitch_secret,
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
        }

        attributes = {"client_id": self.config.twitch_client_id}

        with span(logger, "twitch_api_refresh_user_token", attributes):
            try:
                response = await self.client.post(
                    self._token_url,
                    data=data,
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                )
                response.raise_for_status()
                token_data = response.json()
                logger.info(
                    "Successfully refreshed Twitch user token",
                    extra={"token_type": token_data.get("token_type")},
                )
                return token_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Failed to refresh Twitch token: {e}",
                    extra={"error": str(e), **attributes},
                )
                raise

    async def ensure_token(self):
        """
        Ensure that the access token is valid. Authenticate if necessary.

        Twitch occasionally invalidates app access tokens without warning. To avoid
        serving stale tokens (which surface as 401s from Helix), we now request a
        fresh token ahead of each API call instead of caching between requests.
        """
        with span(
            logger,
            "ensure_token",
            {},
            log_entry=False,
            log_exit=False,
            low_level=True,
        ):
            # Clear any previously cached token before re-authenticating so that
            # downstream calls never reuse a revoked credential.
            self.access_token = None
            self.token_expiry = None
            await self.authenticate()

    def get_headers(self, user_token: Optional[UserAccessToken] = None) -> dict:
        """
        Generate headers for Twitch API requests.
        """
        if user_token:
            token = user_token.access_token
        elif self.access_token:
            token = self.access_token.access_token
        else:
            raise ValueError("Access token is missing. Authenticate first.")
        return {
            "Client-Id": self.config.twitch_client_id,
            "Authorization": f"Bearer {token}",
        }

    async def get_user(self, user_login: str) -> Optional[User]:
        """
        Get user information by username.
        """
        with span(logger, "get_user", {"user_login": user_login}):
            await self.ensure_token()
            params = {"login": user_login}
            url = f"{self._base_url}/users"
            logger.info(f"Fetching user information for login: {user_login}")
            try:
                response = await self.client.get(
                    url, headers=self.get_headers(), params=params
                )
                response.raise_for_status()
                data = response.json().get("data", [])
                if data:
                    user = User(**data[0])
                    logger.info(
                        f"Successfully fetched user: {user.display_name} (ID: {user.id})"
                    )
                    return user
                logger.info(f"No user found with login: {user_login}")
                return None
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(f"Error fetching user: {e}")
                raise

    async def get_channel_info(self, broadcaster_id: str) -> Optional[ChannelInfo]:
        """
        Get channel information for a broadcaster.

        Args:
            broadcaster_id: The ID of the broadcaster

        Returns:
            ChannelInfo object if found, None otherwise
        """
        with span(logger, "get_channel_info", {"broadcaster_id": broadcaster_id}):
            await self.ensure_token()
            params = {"broadcaster_id": broadcaster_id}
            url = f"{self._base_url}/channels"
            logger.info(f"Fetching channel info for broadcaster ID: {broadcaster_id}")
            try:
                response = await self.client.get(
                    url, headers=self.get_headers(), params=params
                )
                response.raise_for_status()
                data = response.json().get("data", [])
                if data:
                    channel_info = ChannelInfo(**data[0])
                    logger.info(
                        f"Successfully fetched channel info for {channel_info.broadcaster_name}"
                    )
                    return channel_info
                logger.info(
                    f"No channel info found for broadcaster ID: {broadcaster_id}"
                )
                return None
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(f"Error fetching channel info: {e}")
                raise

    async def get_streams(
        self,
        user_ids: Optional[List[str]] = None,
        user_logins: Optional[List[str]] = None,
    ) -> List[Stream]:
        """
        Get streams based on user IDs or logins.

        Automatically batches requests if more than 100 IDs/logins provided,
        as per Twitch API limits.

        Args:
            user_ids: Optional list of user IDs to filter streams (auto-batched if > 100)
            user_logins: Optional list of user logins to filter streams (auto-batched if > 100)

        Returns:
            List of Stream objects
        """
        TWITCH_MAX_IDS = 100  # Twitch API hard limit per request

        # Deduplicate inputs early
        if user_ids:
            original_count = len(user_ids)
            user_ids = list(dict.fromkeys(user_ids))
            if len(user_ids) != original_count:
                logger.warning(
                    "Removed duplicate user_ids from get_streams request",
                    extra={
                        "original_count": original_count,
                        "unique_count": len(user_ids),
                        "duplicates_removed": original_count - len(user_ids),
                    },
                )

        if user_logins:
            original_count = len(user_logins)
            user_logins = list(dict.fromkeys(user_logins))
            if len(user_logins) != original_count:
                logger.warning(
                    "Removed duplicate user_logins from get_streams request",
                    extra={
                        "original_count": original_count,
                        "unique_count": len(user_logins),
                        "duplicates_removed": original_count - len(user_logins),
                    },
                )

        # Check if batching is needed
        needs_batching = (user_ids and len(user_ids) > TWITCH_MAX_IDS) or (
            user_logins and len(user_logins) > TWITCH_MAX_IDS
        )

        if needs_batching:
            logger.info(
                "Batching get_streams request due to Twitch API limits",
                extra={
                    "user_ids_count": len(user_ids) if user_ids else 0,
                    "user_logins_count": len(user_logins) if user_logins else 0,
                    "max_per_request": TWITCH_MAX_IDS,
                },
            )

            all_streams = []

            # Batch user_ids if provided
            if user_ids:
                for i in range(0, len(user_ids), TWITCH_MAX_IDS):
                    batch_ids = user_ids[i : i + TWITCH_MAX_IDS]
                    logger.debug(
                        f"Fetching batch {i // TWITCH_MAX_IDS + 1} of "
                        f"{(len(user_ids) + TWITCH_MAX_IDS - 1) // TWITCH_MAX_IDS}"
                    )
                    batch_streams = await self.get_streams(
                        user_ids=batch_ids,
                        user_logins=None,  # Don't mix batching strategies
                    )
                    all_streams.extend(batch_streams)

            # Batch user_logins if provided and not mixed with user_ids
            if user_logins and not user_ids:
                for i in range(0, len(user_logins), TWITCH_MAX_IDS):
                    batch_logins = user_logins[i : i + TWITCH_MAX_IDS]
                    logger.debug(
                        f"Fetching batch {i // TWITCH_MAX_IDS + 1} of "
                        f"{(len(user_logins) + TWITCH_MAX_IDS - 1) // TWITCH_MAX_IDS}"
                    )
                    batch_streams = await self.get_streams(
                        user_ids=None, user_logins=batch_logins
                    )
                    all_streams.extend(batch_streams)

            # Deduplicate streams (same stream ID might appear in multiple batches)
            seen_ids = set()
            unique_streams = []
            for stream in all_streams:
                if stream.id not in seen_ids:
                    seen_ids.add(stream.id)
                    unique_streams.append(stream)

            logger.info(
                "Batched get_streams completed",
                extra={
                    "total_ids": len(user_ids or user_logins or []),
                    "raw_results": len(all_streams),
                    "unique_streams": len(unique_streams),
                },
            )

            return unique_streams

        # Single request path (no batching needed)
        attributes = {
            "user_ids_count": len(user_ids) if user_ids else 0,
            "user_logins_count": len(user_logins) if user_logins else 0,
        }

        with span(
            logger,
            "get_streams",
            attributes,
            log_entry=False,
            log_exit=False,
            low_level=True,
        ):
            await self.ensure_token()
            params = {}
            if user_ids:
                params["user_id"] = user_ids
                logger.debug(f"Filtering streams by user IDs: {user_ids}")
            if user_logins:
                params["user_login"] = user_logins
                logger.debug(f"Filtering streams by user logins: {user_logins}")

            url = f"{self._base_url}/streams"
            logger.debug(f"Fetching streams with {len(params)} filters")

            try:
                response = await self.client.get(
                    url, headers=self.get_headers(), params=params
                )
                response.raise_for_status()
                data = response.json().get("data", [])
                streams = [Stream(**item) for item in data]
                logger.debug(f"Successfully fetched {len(streams)} streams")
                return streams
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(f"Error fetching streams: {e}")
                raise

    async def _send_subscription_request(
        self,
        url: str,
        payload: dict,
        user_token: Optional[UserAccessToken] = None,
    ) -> Optional[EventSubSubscription]:
        """
        Send a subscription request to Twitch EventSub API.

        Args:
            url: The API endpoint URL
            payload: The subscription request payload
            user_token: Optional user access token (required for chat subscriptions)

        Returns:
            EventSubSubscription object if created successfully, None otherwise
        """
        attributes = {
            "url": url,
            "subscription_type": payload.get("type", "unknown"),
            "version": payload.get("version", "unknown"),
        }

        with span(logger, "send_subscription_request", attributes):
            await self.ensure_token()
            logger.info(
                f"Sending EventSub subscription request of type: {payload.get('type', 'unknown')}"
            )
            logger.debug(f"Subscription request details: {payload}")

            try:
                response = await self.client.post(
                    url, headers=self.get_headers(user_token=user_token), json=payload
                )
                response.raise_for_status()
                data = response.json().get("data", [])
                if data:
                    subscription = EventSubSubscription(**data[0])
                    logger.info(
                        f"Successfully created EventSub subscription with ID: {subscription.id}"
                    )
                    return subscription
                logger.warning("Subscription request succeeded but no data returned")
                return None
            except httpx.HTTPStatusError as e:
                status_code = e.response.status_code if e.response else "unknown"
                error_detail = self._extract_response_error_detail(e.response)
                logger.error(
                    "Error creating EventSub subscription "
                    f"(status={status_code}, detail={error_detail}): {e}"
                )
                raise
            except ValidationError as e:
                logger.error(f"Error creating EventSub subscription: {e}")
                raise
            except httpx.HTTPError as e:
                logger.error(f"Error creating EventSub subscription: {e}")
                raise

    @staticmethod
    def _extract_response_error_detail(response: Optional[httpx.Response]) -> str:
        """Extract compact HTTP error detail for actionable log messages."""
        if response is None:
            return "no_response"

        try:
            payload = response.json()
            if isinstance(payload, dict):
                detail = payload.get("message") or payload.get("error") or str(payload)
            else:
                detail = str(payload)
        except Exception:
            detail = (response.text or "").strip() or "<empty>"

        return " ".join(str(detail).split())[:300]

    async def send_chat_message(
        self,
        chat_request: TwitchChatMessageRequest,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> TwitchChatMessageResponse | None:
        """
        Send a chat message to a Twitch channel.

        Args:
            chat_request: The chat message request details
            user_access_token: Optional user access token for sending messages

        Returns:
            TwitchChatMessageResponse: The response from Twitch, or None if the request failed
        """
        attributes = {
            "broadcaster_id": chat_request.broadcaster_id,
            "message_length": len(chat_request.message),
        }

        with span(logger, "send_chat_message", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/chat/messages"

            try:
                logger.info(
                    f"Sending chat message to broadcaster ID {chat_request.broadcaster_id}"
                )
                logger.debug(
                    f"Chat message details: {chat_request.model_dump(exclude={'message'}, exclude_none=True)}"
                )

                response = await self.client.post(
                    url,
                    headers=self.get_headers(user_token=user_access_token),
                    json=chat_request.model_dump(exclude_none=True),
                )
                response.raise_for_status()

                data = response.json().get("data", [])
                if data:
                    result = TwitchChatMessageResponse(**data[0])
                    logger.info(
                        f"Chat message sent successfully, message ID: {result.message_id}"
                    )
                    return result
                else:
                    logger.warning("Chat message sent but no data returned in response")
                    return None

            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Failed to send chat message with status {e.response.status_code}: {e}"
                )
                return None
            except ValidationError as e:
                logger.error(f"Failed to parse chat message response: {e}")
                return None
            except httpx.HTTPError as e:
                logger.error(f"HTTP error sending chat message: {e}")
                return None
            except Exception as e:
                logger.error(f"Unexpected error sending chat message: {e}")
                return None

    async def create_eventsub_subscription(
        self,
        request: EventSubSubscriptionRequest,
        user_token: Optional[UserAccessToken] = None,
    ) -> Optional[EventSubSubscription]:
        """
        Create a new EventSub subscription.

        Args:
            request: The subscription request details.
            user_token: Optional user access token (required for chat subscriptions).
        Returns:
            Optional[EventSubSubscription]: The created subscription details.
        """
        logger.info(
            f"Creating EventSub subscription of type: {request.type}, version: {request.version}"
        )
        logger.debug(f"Subscription condition: {request.condition}")
        logger.debug(f"Transport method: {request.transport.method}")

        await self.ensure_token()
        url = f"{self._base_url}/eventsub/subscriptions"
        payload = {
            "type": request.type,
            "version": request.version,
            "condition": request.condition,
            "transport": request.transport.model_dump(exclude_none=True),
        }
        return await self._send_subscription_request(url, payload, user_token)

    async def delete_eventsub_subscription(self, subscription_id: str) -> bool:
        """
        Delete an existing EventSub subscription.

        Args:
            subscription_id (str): The ID of the subscription to delete.

        Returns:
            bool: True if deletion was successful, False otherwise.
        """
        with span(
            logger, "delete_eventsub_subscription", {"subscription_id": subscription_id}
        ):
            await self.ensure_token()
            url = f"{self._base_url}/eventsub/subscriptions"
            params = {"id": subscription_id}

            logger.info(f"Deleting EventSub subscription with ID: {subscription_id}")

            try:
                response = await self.client.delete(
                    url, headers=self.get_headers(), params=params
                )
                response.raise_for_status()
                logger.info(
                    f"Successfully deleted EventSub subscription with ID: {subscription_id}"
                )
                return True
            except httpx.HTTPError as e:
                logger.error(f"Error deleting EventSub subscription: {e}")
                return False

    async def list_eventsub_subscriptions(self) -> List[EventSubSubscription]:
        """
        List all EventSub subscriptions with pagination support.

        Returns:
            List[EventSubSubscription]: A complete list of all subscriptions across all pages.
        """
        with span(logger, "list_eventsub_subscriptions", {}):
            await self.ensure_token()
            url = f"{self._base_url}/eventsub/subscriptions"

            logger.info("Fetching list of EventSub subscriptions (with pagination)")

            try:
                all_subscriptions = []
                cursor = None
                page = 1

                while True:
                    # Build params with cursor if we have one
                    params = {}
                    if cursor:
                        params["after"] = cursor

                    response = await self.client.get(
                        url, headers=self.get_headers(), params=params
                    )
                    response.raise_for_status()

                    response_data = response.json()
                    data = response_data.get("data", [])
                    pagination = response_data.get("pagination", {})

                    # Parse subscriptions from this page
                    page_subs = [EventSubSubscription(**item) for item in data]
                    all_subscriptions.extend(page_subs)

                    logger.debug(f"Fetched page {page}: {len(page_subs)} subscriptions")

                    # Check for next page
                    cursor = pagination.get("cursor")
                    if not cursor:
                        break  # No more pages

                    page += 1

                subscriptions = all_subscriptions

                # Group subscriptions by type for more informative logging
                subscription_types = {}
                for sub in subscriptions:
                    if sub.type not in subscription_types:
                        subscription_types[sub.type] = 0
                    subscription_types[sub.type] += 1

                logger.info(
                    f"Found {len(subscriptions)} EventSub subscriptions across {page} page(s)"
                )
                for sub_type, count in subscription_types.items():
                    logger.debug(f"  - {sub_type}: {count} subscription(s)")

                return subscriptions
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(f"Error listing EventSub subscriptions: {e}")
                raise

    async def get_eventsub_notification(
        self, payload: dict
    ) -> Optional[EventSubNotification]:
        """
        Validate and parse an EventSub notification payload.

        Args:
            payload (dict): The JSON payload received from Twitch.

        Returns:
            Optional[EventSubNotification]: The parsed notification.
        """
        try:
            return EventSubNotification(**payload)
        except ValidationError as e:
            logger.error(f"Error parsing EventSub notification: {e}")
            return None

    # New Methods for Conduit Management

    async def create_eventsub_conduit(
        self, shard_count: int
    ) -> Optional[EventSubConduit]:
        """
        Create a new EventSub Conduit with the specified number of shards.

        Args:
            shard_count (int): Number of shards to create.

        Returns:
            Optional[EventSubConduit]: The created Conduit details.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/conduits"
        payload = {"shard_count": shard_count}
        try:
            response = await self.client.post(
                url, headers=self.get_headers(), json=payload
            )
            response.raise_for_status()
            data = response.json().get("data", [])
            if data:
                return EventSubConduit(**data[0])
            return None
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error creating EventSub conduit: {e}")
            raise

    async def update_eventsub_conduit_shards(
        self,
        conduit_id: str,
        shards: Sequence[
            Union[
                EventSubConduitShard,
                Tuple[str, EventSubTransport],
                EventSubTransport,
            ]
        ],
    ) -> List[EventSubConduitShard]:
        """
        Assign transports to shards within a Conduit.

        Args:
            conduit_id (str): The ID of the Conduit.
            shards (List[EventSubConduitShardTransport]): List of shard transport configurations.

        Returns:
            List[EventSubConduitShard]: Updated shard details.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/conduits/shards"
        shard_updates: List[EventSubConduitShard] = []
        for idx, shard in enumerate(shards):
            if isinstance(shard, EventSubConduitShard):
                shard_updates.append(shard)
            elif isinstance(shard, tuple):
                shard_id, transport = shard
                shard_updates.append(
                    EventSubConduitShard(id=str(shard_id), transport=transport)
                )
            else:
                shard_updates.append(EventSubConduitShard(id=str(idx), transport=shard))
        payload = {
            "conduit_id": conduit_id,
            "shards": [shard.model_dump(exclude_none=True) for shard in shard_updates],
        }
        logger.debug(f"Updating EventSub conduit shards: {payload}")
        try:
            response = await self.client.patch(
                url, headers=self.get_headers(), json=payload
            )
            response.raise_for_status()
            logger.debug(f"Response: {response.json()}")
            data = response.json().get("data", [])
            updated_shards = [EventSubConduitShard(**shard) for shard in data]
            return updated_shards
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error updating EventSub conduit shards: {e}")
            raise

    async def list_eventsub_conduits(self) -> List[EventSubConduit]:
        """
        List all EventSub Conduits.

        Returns:
            List[EventSubConduit]: A list of existing conduits.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/conduits"
        try:
            logger.debug(f"Fetching EventSub conduits from {url}")
            # logger.debug(f"Headers: {self.get_headers()}")
            response = await self.client.get(url, headers=self.get_headers())
            logger.info(f"Response: {response.json()}")
            response.raise_for_status()
            data = response.json().get("data", [])
            conduits = [EventSubConduit(**item) for item in data]
            return conduits
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error listing EventSub conduits: {e}")
            raise

    async def get_conduit_shards(self, conduit_id: str) -> List[EventSubConduitShard]:
        """
        Get all shards for a specific EventSub Conduit.

        Args:
            conduit_id (str): The ID of the Conduit.

        Returns:
            List[EventSubConduitShard]: List of shards for the Conduit.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/conduits/shards"
        params = {"conduit_id": conduit_id}
        try:
            response = await self.client.get(
                url, headers=self.get_headers(), params=params
            )
            response.raise_for_status()
            data = response.json().get("data", [])
            shards = [EventSubConduitShard(**item) for item in data]
            return shards
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error fetching EventSub conduit shards: {e}")
            raise

    async def delete_eventsub_conduit(self, conduit_id: str) -> bool:
        """
        Delete an existing EventSub Conduit.

        Args:
            conduit_id (str): The ID of the Conduit to delete.

        Returns:
            bool: True if deletion was successful, False otherwise.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/conduits"
        params = {"id": conduit_id}
        try:
            response = await self.client.delete(
                url, headers=self.get_headers(), params=params
            )
            response.raise_for_status()
            return True
        except httpx.HTTPError as e:
            logger.error(f"Error deleting EventSub conduit: {e}")
            return False

    async def subscribe_to_conduit_event(
        self, conduit_id: str, subscription_type: str, version: str, condition: dict
    ) -> Optional[EventSubSubscription]:
        """
        Create an EventSub subscription using a Conduit.

        Args:
            conduit_id (str): The ID of the Conduit.
            subscription_type (str): Event type (e.g., 'channel.subscribe').
            version (str): Event version.
            condition (dict): Subscription condition.

        Returns:
            Optional[EventSubSubscription]: The created subscription.
        """
        await self.ensure_token()
        url = f"{self._base_url}/eventsub/subscriptions"
        payload = {
            "type": subscription_type,
            "version": version,
            "condition": condition,
            "transport": {"method": "conduit", "conduit_id": conduit_id},
        }
        return await self._send_subscription_request(url, payload)

    async def mark_stream(
        self, user_access_token: UserAccessToken, mark_stream_request: MarkStreamRequest
    ):
        """
        Mark a stream as processed or handled.
        """
        # Placeholder for marking a stream
        logger.info(f"Marking stream: {mark_stream_request}")

        url = f"{self._base_url}/streams/markers"
        try:
            res = await self.client.post(
                url,
                headers=self.get_headers(user_token=user_access_token),
                json=mark_stream_request.model_dump(exclude_none=True),
            )
            res.raise_for_status()
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error marking stream: {e}")
            raise
        data = res.json().get("data", [])
        return MarkStreamResponse(**data[0]) if data else None

    async def get_stream_markers(
        self,
        user_access_token: UserAccessToken,
        user_id: Optional[str] = None,
        video_id: Optional[str] = None,
        first: Optional[int] = None,
        after: Optional[str] = None,
    ) -> GetStreamMarkersResponse:
        """
        Get stream markers for a user or video.

        The request requires a user access token that includes the
        channel:manage:broadcast or channel:read:broadcast scope.

        Args:
            user_auth_code: Twitch user authorization code
            user_id: ID of the broadcaster to get markers for (most recent VOD)
            video_id: ID of the specific VOD to get markers for
            first: Maximum number of markers to return per page
            after: Cursor for pagination

        Returns:
            GetStreamMarkersResponse with user's stream markers data

        Raises:
            httpx.HTTPError: If the request fails
        """

        params = {}
        if user_id:
            params["user_id"] = user_id
        if video_id:
            params["video_id"] = video_id
        if first:
            params["first"] = first
        if after:
            params["after"] = after

        url = f"{self._base_url}/streams/markers"

        try:
            response = await self.client.get(
                url,
                headers=self.get_headers(user_token=user_access_token),
                params=params,
            )
            response.raise_for_status()
            return GetStreamMarkersResponse(**response.json())
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                logger.info("No VODs found for this user")
                return GetStreamMarkersResponse(data=[], pagination={})
            logger.error(f"Error getting stream markers: {e}")
            raise
        except (httpx.HTTPError, ValidationError) as e:
            logger.error(f"Error getting stream markers: {e}")
            raise

    async def close(self):
        """
        Close the HTTP client session.
        """
        await self.client.aclose()
