"""
Kick API client for interacting with the Kick API.
"""

import secrets
from typing import Optional, List, Any, Tuple

import httpx
import pkce
from pydantic import ValidationError, Field
from pydantic_settings import BaseSettings

from common.logging import get_logger, span
from common.models.common import AppAccessToken, UserAccessToken
from common.platform.kick.model import (
    KickTokenIntrospectResponse,
    KickUserResponse,
    KickChannelResponse,
    KickLivestreamResponse,
    KickEventSubscriptionsResponse,
    KickEventSubscriptionRequest,
    KickEventSubscriptionResponse,
    KickAuthorizeParams,
    KickChatMessageRequest,
    KickChatMessageResponse,
    KickChatMessageResponseData,
)

logger = get_logger(__name__)


def generate_pkce_pair():
    return pkce.generate_pkce_pair()


class KickApiConfig(BaseSettings):
    """
    Configuration settings for the Kick API.

    Attributes:
    -----------
    client_id: str
        Kick client ID
    secret: str
        Kick secret
    base_url: str
        Kick API base URL
    """

    kick_client_id: str = ""
    kick_secret: str = ""
    kick_base_url: str = Field(default="https://api.kick.com/public/v1")


class KickApi:
    """
    Kick API client for interacting with the Kick API.

    Attributes:
    -----------
    config: KickApiConfig
        Kick API configuration settings

    Methods:
    --------
    authenticate():
        Authenticate with Kick using Client Credentials Flow.
    ensure_token():
        Ensure that the access token is valid. Authenticate if necessary.
    get_headers():
        Generate headers for Kick API requests.
    introspect_token():
        Get information about the current token.
    get_users(user_ids: Optional[List[int]] = None):
        Get user information based on provided user IDs.
    get_channels(broadcaster_user_ids: Optional[List[int]] = None, slugs: Optional[List[str]] = None):
        Get channel information based on provided broadcaster user IDs or channel slugs.
    update_channel(category_id: int, stream_title: str):
        Update livestream metadata for a channel.
    get_livestreams(broadcaster_user_id: Optional[int] = None, category_id: Optional[int] = None,
                   language: Optional[str] = None, limit: Optional[int] = None, sort: Optional[str] = None):
        Get livestreams based on various filters.
    get_event_subscriptions():
        Get all event subscriptions.
    create_event_subscriptions(request: KickEventSubscriptionRequest):
        Create new event subscriptions.
    delete_event_subscription(subscription_id: str):
        Delete an event subscription.
    close():
        Close the HTTP client session.
    """

    def __init__(
        self,
        config: KickApiConfig,
        access_token: Optional[AppAccessToken] = None,
    ) -> None:
        self._base_url = config.kick_base_url
        self._token_url = "https://id.kick.com/oauth/token"
        self.app_access_token = access_token
        self.config = config
        self.client = httpx.AsyncClient(timeout=30.0)

    async def _generate_app_access_token(self):
        """
        Authenticate with Kick using Client Credentials Flow.
        """
        data = {
            "client_id": self.config.kick_client_id,
            "client_secret": self.config.kick_secret,
            "grant_type": "client_credentials",
        }

        with span(
            logger, "kick_api_authenticate", {"client_id": self.config.kick_client_id}
        ):
            try:
                response = await self.client.post(self._token_url, data=data)
                response.raise_for_status()
                token_data = response.json()
                self.app_access_token = AppAccessToken(**token_data)
                logger.info(
                    "Authenticated with Kick API",
                    extra={"expires_in": self.app_access_token.expires_in},
                )
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Authentication failed (Kick): {e}", extra={"error": str(e)}
                )
                raise

    async def ensure_token(self):
        """
        Ensure that the access token is valid. Authenticate if necessary.
        """
        if self.app_access_token is None:
            logger.info("No access token available (Kick), generating new token")
            await self._generate_app_access_token()
        elif self.app_access_token.expires_in < 300.0:
            logger.info(
                "Access token (Kick) expiring soon",
                extra={"expires_in": self.app_access_token.expires_in},
            )
            await self._generate_app_access_token()

    def get_headers(self, user_token: Optional[UserAccessToken] = None) -> dict:
        """
        Generate headers for Kick API requests.
        """
        if not self.app_access_token and not user_token:
            raise ValueError("Access token is missing. Authenticate first.")
        access_token = (
            user_token.access_token
            if user_token
            else self.app_access_token.access_token
        )
        return {
            "Authorization": f"Bearer {access_token}",
            "Accept": "application/json",
        }

    async def exchange_oauth_code(
        self, code: str, code_verifier: str, redirect_uri: str = "http://localhost:3000"
    ) -> Any:
        """
        Exchange the OAuth code for an access token.
        """
        data = {
            "client_id": self.config.kick_client_id,
            "client_secret": self.config.kick_secret,
            "code": code,
            "grant_type": "authorization_code",
            "redirect_uri": redirect_uri,
            "code_verifier": code_verifier,
        }

        attributes = {
            "client_id": self.config.kick_client_id,
            "redirect_uri": redirect_uri,
        }

        with span(logger, "kick_api_exchange_oauth_code", attributes):
            try:
                await self.ensure_token()
                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(
                    "OAuth code exchanged successfully",
                    extra={"token_type": token_data.get("token_type")},
                )
                return token_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Failed to exchange OAuth code: {e}",
                    extra={"error": str(e), **attributes},
                )
                raise

    def get_authorize_params(
        self, redirect_uri: str = "http://localhost:3000"
    ) -> Tuple[KickAuthorizeParams, str]:
        attributes = {
            "client_id": self.config.kick_client_id,
            "redirect_uri": redirect_uri,
        }

        with span(logger, "kick_api_get_authorize_params", attributes):
            code_verifier, code_challenge = generate_pkce_pair()
            params = {
                "client_id": self.config.kick_client_id,
                "redirect_uri": redirect_uri,
                "response_type": "code",
                "code_challenge": code_challenge,
                "code_challenge_method": "S256",
                "scope": "user:read channel:read chat:write streamkey:read events:subscribe",
                "state": secrets.token_urlsafe(32),
            }
            try:
                auth_params = KickAuthorizeParams(**params)
                logger.info("Generated authorization parameters", extra=attributes)
                return auth_params, code_verifier
            except ValidationError as e:
                logger.error(
                    f"Failed to generate authorization parameters: {e}",
                    extra={"error": str(e), **attributes},
                )
                raise

    async def introspect_token(self) -> KickTokenIntrospectResponse:
        """
        Get information about the current token.
        """
        with span(logger, "kick_api_introspect_token"):
            await self.ensure_token()
            url = f"{self._base_url}/token/introspect"
            try:
                response = await self.client.post(url, headers=self.get_headers())
                response.raise_for_status()
                token_data = KickTokenIntrospectResponse(**response.json())
                logger.info(
                    "Token introspection (Kick) successful",
                    extra={
                        "active": token_data.active,
                        "client_id": token_data.client_id,
                    },
                )
                return token_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error introspecting token: {e}",
                    extra={"error": str(e), "url": url},
                )
                raise

    async def get_users(
        self,
        user_ids: Optional[List[int] | int] = None,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> KickUserResponse:
        """
        Get user information based on provided user IDs.
        If no user IDs are specified, returns information for the currently authorized user.
        """
        attributes = {"user_ids": user_ids} if user_ids else {}

        with span(logger, "kick_api_get_users", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/users"
            params = {}
            response_json: dict[str, Any] | list[Any] | None = None
            if user_ids:
                params["id"] = user_ids
            try:
                response = await self.client.get(
                    url,
                    headers=self.get_headers(user_token=user_access_token),
                    params=params,
                )
                response.raise_for_status()
                response_json = response.json()
                user_data = KickUserResponse(**response_json)
                logger.info(
                    "Successfully fetched user (Kick) data",
                    extra={
                        "user_count": len(user_data.data)
                        if hasattr(user_data, "data")
                        else 1
                    },
                )
                return user_data
            except ValidationError as e:
                payload_keys: list[str] = []
                first_user_keys: list[str] = []
                missing_fields: list[str] = []

                if isinstance(response_json, dict):
                    payload_keys = sorted(str(k) for k in response_json.keys())
                    users_data = response_json.get("data")
                    if (
                        isinstance(users_data, list)
                        and users_data
                        and isinstance(users_data[0], dict)
                    ):
                        first_user_keys = sorted(str(k) for k in users_data[0].keys())

                for validation_error in e.errors():
                    if validation_error.get("type") == "missing":
                        location = validation_error.get("loc")
                        if location:
                            missing_fields.append(
                                ".".join(str(part) for part in location)
                            )

                logger.error(
                    "Failed to parse Kick users response",
                    extra={
                        "error": str(e),
                        "url": url,
                        "payload_keys": payload_keys,
                        "first_user_keys": first_user_keys,
                        "missing_fields": missing_fields,
                        **attributes,
                    },
                )
                raise
            except httpx.HTTPError as e:
                logger.error(
                    f"Error fetching users: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

    async def get_channels(
        self,
        broadcaster_user_ids: Optional[List[int]] = None,
        slugs: Optional[List[str]] = None,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> KickChannelResponse:
        """
        Get channel information based on provided broadcaster user IDs or channel slugs.
        You can either:
        - Provide no parameters (returns information for the currently authenticated user)
        - Provide only broadcaster_user_id parameters (up to 50)
        - Provide only slug parameters (up to 50, each max 25 characters)
        Note: You cannot mix broadcaster_user_id and slug parameters in the same request.
        """
        attributes = {}
        if broadcaster_user_ids:
            attributes["broadcaster_user_ids"] = broadcaster_user_ids
        elif slugs:
            attributes["slugs"] = slugs

        with span(logger, "kick_api_get_channels", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/channels"
            params = {}
            if broadcaster_user_ids:
                params["broadcaster_user_id"] = broadcaster_user_ids
            elif slugs:
                params["slug"] = slugs
            try:
                response = await self.client.get(
                    url,
                    headers=self.get_headers(user_token=user_access_token),
                    params=params,
                )
                response.raise_for_status()
                logger.info(
                    "Kick channel response",
                    extra={"url": url, "params": params, "response": response.json()},
                )
                channel_data = KickChannelResponse(**response.json())
                logger.info(
                    "Successfully fetched channel data",
                    extra={
                        "channel_count": len(channel_data.data)
                        if hasattr(channel_data, "data")
                        else 1
                    },
                )
                return channel_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error fetching (Kick) channels: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

    async def update_channel(
        self,
        category_id: int,
        stream_title: str,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> bool:
        """
        Update livestream metadata for a channel.
        """
        attributes = {"category_id": category_id, "stream_title": stream_title}

        with span(logger, "kick_api_update_channel", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/channels"
            data = {"category_id": category_id, "stream_title": stream_title}
            try:
                response = await self.client.patch(
                    url,
                    headers=self.get_headers(user_token=user_access_token),
                    json=data,
                )
                response.raise_for_status()
                logger.info(
                    "Successfully updated (Kick) channel metadata", extra=attributes
                )
                return True
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error updating channel: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

    async def get_livestreams(
        self,
        broadcaster_user_id: Optional[int] = None,
        category_id: Optional[int] = None,
        language: Optional[str] = None,
        limit: Optional[int] = None,
        sort: Optional[str] = None,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> KickLivestreamResponse:
        """
        Get livestreams based on various filters.
        """
        attributes = {}
        if broadcaster_user_id:
            attributes["broadcaster_user_id"] = broadcaster_user_id
        if category_id:
            attributes["category_id"] = category_id
        if language:
            attributes["language"] = language
        if limit:
            attributes["limit"] = min(limit, 100)
        if sort:
            attributes["sort"] = sort

        with span(logger, "kick_api_get_livestreams", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/livestreams"
            params = {}
            if broadcaster_user_id:
                params["broadcaster_user_id"] = broadcaster_user_id
            if category_id:
                params["category_id"] = category_id
            if language:
                params["language"] = language
            if limit:
                params["limit"] = min(limit, 100)  # Max 100 as per API docs
            if sort:
                params["sort"] = sort
            try:
                response = await self.client.get(
                    url,
                    headers=self.get_headers(user_token=user_access_token),
                    params=params,
                )
                response.raise_for_status()
                livestream_data = KickLivestreamResponse(**response.json())
                logger.info(
                    "Successfully fetched kick livestream data",
                    extra={
                        "livestream_count": len(livestream_data.data)
                        if hasattr(livestream_data, "data")
                        else 0,
                        "livestream_data": livestream_data,
                    },
                )
                return livestream_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error fetching livestreams: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

    async def get_event_subscriptions(self) -> KickEventSubscriptionsResponse:
        """
        Get all event subscriptions.
        """
        with span(logger, "kick_api_get_event_subscriptions"):
            await self.ensure_token()
            url = f"{self._base_url}/events/subscriptions"
            try:
                response = await self.client.get(url, headers=self.get_headers())
                response.raise_for_status()
                subscription_data = KickEventSubscriptionsResponse(**response.json())
                logger.info(
                    "Successfully fetched event (Kick) subscriptions",
                    extra={
                        "subscription_count": len(subscription_data.data)
                        if hasattr(subscription_data, "data")
                        else 0,
                        "subcription_data": subscription_data,
                    },
                )
                return subscription_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error fetching event (Kick) subscriptions: {e}",
                    extra={"error": str(e), "url": url},
                )
                raise

    async def create_event_subscriptions(
        self,
        request: KickEventSubscriptionRequest,
        user_access_token: Optional[UserAccessToken] = None,
    ) -> KickEventSubscriptionResponse:
        """
        Create new event subscriptions.
        """
        # Extract relevant attributes for logging
        attributes = {
            "broadcaster_user_id": request.broadcaster_user_id,
            "method": request.method,
            "events_count": len(request.events) if request.events else 0,
            "webhook_url": request.webhook_url,
            "user_access_token": user_access_token,
            "request_model_dump_test": request.model_dump(exclude_none=True)
            if hasattr(request, "webhook_url") and request.webhook_url
            else None,
        }

        with span(logger, "kick_api_create_event_subscriptions", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/events/subscriptions"
            try:
                response = await self.client.post(
                    url,
                    headers=self.get_headers(user_token=self.app_access_token),
                    json=request.model_dump(exclude_none=True),
                )
                response.raise_for_status()
                subscription_data = KickEventSubscriptionResponse(**response.json())
                logger.info(
                    "Successfully created (Kick) event subscription",
                    extra={
                        "subscription_ids": [
                            data.subscription_id for data in subscription_data.data
                        ]
                        if hasattr(subscription_data, "data")
                        else None,
                        "subscription_data": subscription_data,
                    },
                )
                return subscription_data
            except (httpx.HTTPError, ValidationError) as e:
                logger.error(
                    f"Error creating (Kick) event subscriptions: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

    async def delete_event_subscription(self, subscription_id: str) -> bool:
        """
        Delete an event subscription.
        """
        attributes = {"subscription_id": subscription_id}

        with span(logger, "kick_api_delete_event_subscription", attributes):
            await self.ensure_token()
            url = f"{self._base_url}/events/subscriptions"
            params = {"id": [subscription_id]}
            try:
                response = await self.client.delete(
                    url, headers=self.get_headers(), params=params
                )
                response.raise_for_status()
                logger.info(
                    "Successfully deleted (Kick) event subscription", extra=attributes
                )
                return True
            except httpx.HTTPError as e:
                logger.error(
                    f"Error deleting (Kick) event subscription: {e}",
                    extra={"error": str(e), "url": url, **attributes},
                )
                raise

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

        Args:
            chat_request: The chat message request details
            user_access_token: The user access token to use for authentication

        Returns:
            KickChatMessageResponse: The response from Kick, or None if the request failed
        """
        attributes = {
            "broadcaster_user_id": chat_request.broadcaster_user_id,
            "message_length": len(chat_request.content),
        }

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

            try:
                logger.info(
                    f"Sending chat message to Kick broadcaster ID {chat_request.broadcaster_user_id}"
                )
                logger.debug(
                    f"Chat message details: {chat_request.model_dump(exclude={'content'}, 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()

                # Parse the response from the API
                response_data = response.json()
                result = KickChatMessageResponse(**response_data)
                logger.info(
                    f"Chat message sent successfully to Kick, message ID: {result.data.message_id}"
                )
                return result

            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Failed to send chat message to Kick with status {e.response.status_code}: {e}"
                )
                # Create a response with error information
                message_id = f"error-{secrets.token_hex(8)}"
                return KickChatMessageResponse(
                    data=KickChatMessageResponseData(
                        is_sent=False, message_id=message_id
                    ),
                    message="Failed to send message",
                    error=str(e),
                )
            except ValidationError as e:
                logger.error(f"Failed to parse Kick chat message response: {e}")
                return None
            except httpx.HTTPError as e:
                logger.error(f"HTTP error sending chat message to Kick: {e}")
                return None
            except Exception as e:
                logger.error(f"Unexpected error sending chat message to Kick: {e}")
                return None

    async def close(self):
        """
        Close the HTTP client session.
        """
        logger.info("Closing Kick API client session")
        await self.client.aclose()
