"""X (Twitter) OAuth provider with API v2 support."""

import base64
from dataclasses import dataclass
from typing import Any, Optional
from urllib.parse import urlencode

from nospoon_integrations.core.errors import ProviderAPIError
from nospoon_integrations.core.types import (
    ProviderConfig,
    ProviderEndpoints,
    TokenRefreshResult,
    TokenStorage,
)
from nospoon_integrations.providers.base_provider import BaseProvider

X_ENDPOINTS = ProviderEndpoints(
    auth_url="https://x.com/i/oauth2/authorize",
    token_url="https://api.x.com/2/oauth2/token",
    revoke_url="https://api.x.com/2/oauth2/revoke",
    user_info_url="https://api.x.com/2/users/me",
)

# ============ X API Scopes ============

X_TWEET_READ_SCOPE = "tweet.read"
X_TWEET_WRITE_SCOPE = "tweet.write"
X_USERS_READ_SCOPE = "users.read"
X_OFFLINE_ACCESS_SCOPE = "offline.access"
X_MEDIA_WRITE_SCOPE = "media.write"


@dataclass
class XUserInfo:
    """X user info."""

    id: str
    name: str
    username: str
    profile_image_url: Optional[str] = None
    description: Optional[str] = None


@dataclass
class CreatePostParams:
    """Parameters for creating a post (tweet)."""

    text: str
    reply_to_tweet_id: Optional[str] = None
    quote_tweet_id: Optional[str] = None
    media_ids: Optional[list[str]] = None
    poll_options: Optional[list[str]] = None
    poll_duration_minutes: Optional[int] = None


@dataclass
class XPostResult:
    """Result from creating a post."""

    id: str
    text: str


@dataclass
class UploadMediaParams:
    """Parameters for uploading media."""

    media: bytes
    mime_type: str
    alt_text: Optional[str] = None


@dataclass
class XMediaResult:
    """Result from uploading media."""

    media_id: str
    expires_after_secs: Optional[int] = None


class XProvider(BaseProvider):
    """
    X (Twitter) OAuth provider with API v2 support.

    Uses OAuth 2.0 with PKCE for authentication.
    Supports tweet creation, media upload, and user profile access.
    """

    def __init__(self, config: ProviderConfig, storage: TokenStorage) -> None:
        default_scopes = config.scopes or [
            X_TWEET_READ_SCOPE,
            X_TWEET_WRITE_SCOPE,
            X_USERS_READ_SCOPE,
            X_OFFLINE_ACCESS_SCOPE,
        ]
        super().__init__(
            "x",
            X_ENDPOINTS,
            ProviderConfig(
                client_id=config.client_id,
                client_secret=config.client_secret,
                scopes=default_scopes,
                redirect_uri=config.redirect_uri,
                pkce=True,
            ),
            storage,
        )

    def _get_token_request_headers(self) -> dict[str, str]:
        """X uses Basic auth for token requests."""
        credentials = base64.b64encode(
            f"{self._config.client_id}:{self._config.client_secret}".encode()
        ).decode()
        return {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": f"Basic {credentials}",
        }

    def _get_token_request_body(
        self, code: str, redirect_uri: str, code_verifier: Optional[str] = None
    ) -> str:
        """X token request body - include client_id in body for PKCE."""
        params: dict[str, str] = {
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": redirect_uri,
            "client_id": self._config.client_id,
        }
        if code_verifier:
            params["code_verifier"] = code_verifier
        return urlencode(params)

    def _get_refresh_token_request_body(self, refresh_token: str) -> str:
        """X refresh token body with client_id."""
        return urlencode(
            {
                "grant_type": "refresh_token",
                "refresh_token": refresh_token,
                "client_id": self._config.client_id,
            }
        )

    def _parse_refresh_token_response(self, data: dict[str, Any]) -> TokenRefreshResult:
        """X returns a new refresh token on every refresh (rotating tokens)."""
        return TokenRefreshResult(
            access_token=data["access_token"],
            refresh_token=data.get("refresh_token"),
            expires_in=data.get("expires_in", 7200),  # 2 hours default
            scope=data.get("scope"),
        )

    # ============ X-specific API methods ============

    async def get_user_info(self, user_id: str) -> XUserInfo:
        """
        Get user info from X.

        Args:
            user_id: User ID (internal, for token retrieval)

        Returns:
            X user info
        """
        access_token = await self.get_valid_token(user_id)

        response = await self._client.get(
            "https://api.x.com/2/users/me",
            params={"user.fields": "id,name,username,profile_image_url,description"},
            headers={"Authorization": f"Bearer {access_token}"},
        )

        if not response.is_success:
            raise ProviderAPIError(
                "x",
                response.status_code,
                "Failed to get user info",
                response.text,
            )

        data: dict[str, Any] = response.json()["data"]
        return XUserInfo(
            id=data["id"],
            name=data["name"],
            username=data["username"],
            profile_image_url=data.get("profile_image_url"),
            description=data.get("description"),
        )

    async def create_post(self, user_id: str, params: CreatePostParams) -> XPostResult:
        """
        Create a post (tweet) on X.

        Args:
            user_id: User ID
            params: Post parameters

        Returns:
            Post result with ID and text
        """
        access_token = await self.get_valid_token(user_id)

        body: dict[str, Any] = {"text": params.text}

        if params.reply_to_tweet_id:
            body["reply"] = {"in_reply_to_tweet_id": params.reply_to_tweet_id}

        if params.quote_tweet_id:
            body["quote_tweet_id"] = params.quote_tweet_id

        if params.media_ids:
            body["media"] = {"media_ids": params.media_ids}

        if params.poll_options:
            body["poll"] = {
                "options": params.poll_options,
                "duration_minutes": params.poll_duration_minutes or 60,
            }

        response = await self._client.post(
            "https://api.x.com/2/tweets",
            headers={
                "Authorization": f"Bearer {access_token}",
                "Content-Type": "application/json",
            },
            json=body,
        )

        if not response.is_success:
            raise ProviderAPIError(
                "x",
                response.status_code,
                "Failed to create post",
                response.text,
            )

        data: dict[str, Any] = response.json()["data"]
        return XPostResult(id=data["id"], text=data["text"])

    async def upload_media(self, user_id: str, params: UploadMediaParams) -> XMediaResult:
        """
        Upload media to X.

        Uses v1.1 media upload endpoint for simple uploads.

        Args:
            user_id: User ID
            params: Upload parameters

        Returns:
            Media result with media ID
        """
        access_token = await self.get_valid_token(user_id)

        category = self._get_media_category(params.mime_type)

        response = await self._client.post(
            "https://upload.x.com/1.1/media/upload.json",
            headers={"Authorization": f"Bearer {access_token}"},
            files={"media": ("media", params.media, params.mime_type)},
            data={"media_category": category},
        )

        if not response.is_success:
            raise ProviderAPIError(
                "x",
                response.status_code,
                "Failed to upload media",
                response.text,
            )

        result: dict[str, Any] = response.json()

        # Set alt text if provided
        if params.alt_text:
            await self._set_media_alt_text(access_token, result["media_id_string"], params.alt_text)

        return XMediaResult(
            media_id=result["media_id_string"],
            expires_after_secs=result.get("expires_after_secs"),
        )

    async def delete_post(self, user_id: str, tweet_id: str) -> None:
        """
        Delete a post (tweet) on X.

        Args:
            user_id: User ID
            tweet_id: Tweet ID to delete
        """
        access_token = await self.get_valid_token(user_id)

        response = await self._client.delete(
            f"https://api.x.com/2/tweets/{tweet_id}",
            headers={"Authorization": f"Bearer {access_token}"},
        )

        if not response.is_success:
            raise ProviderAPIError(
                "x",
                response.status_code,
                "Failed to delete post",
                response.text,
            )

    @staticmethod
    def _get_media_category(mime_type: str) -> str:
        """Determine media category from MIME type."""
        if mime_type.startswith("video/"):
            return "tweet_video"
        if mime_type == "image/gif":
            return "tweet_gif"
        return "tweet_image"

    async def _set_media_alt_text(self, access_token: str, media_id: str, alt_text: str) -> None:
        """Set alt text on uploaded media."""
        await self._client.post(
            "https://upload.x.com/1.1/media/metadata/create.json",
            headers={
                "Authorization": f"Bearer {access_token}",
                "Content-Type": "application/json",
            },
            json={"media_id": media_id, "alt_text": {"text": alt_text}},
        )
