import base64
from typing import Optional

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

from common.logging import get_logger, span
from common.models.common import Platform, UserAccessTokenUpdate, UserAccessToken

from datetime import datetime, timedelta, timezone


logger = get_logger(__name__)


class SocialMediaConfig(BaseSettings):
    """
    Configuration settings for the Social Media API (youtube, facebook, instagram, tiktok, etc).

    Attributes:
    -----------
    youtube_client_id: str
        youtube client ID
    youtube_secret: str
        youtube secret
    youtube_base_url: str
        youtube authorize url
    youtube_token_url: str
        youtube exchange token url

    twitter_client_id: str
        twitter client id
    twitter_secret: str
        twitter secret
    twitter_token_url: str
        twitter exchange token url

    reddit_client_id: str
        reddit client id (app id)
    reddit_secret: str
        reddit secret
    reddit_token_url: str
        reddit exchange token url
    """

    youtube_client_id: Optional[str] = None
    youtube_secret: Optional[str] = None
    youtube_base_url: str = Field(default="https://www.googleapis.com/youtube/v3")
    youtube_token_url: str = Field(default="https://oauth2.googleapis.com/token")

    twitter_client_id: Optional[str] = None
    twitter_secret: Optional[str] = None
    twitter_token_url: str = Field(default="https://api.twitter.com/2/oauth2/token")

    reddit_client_id: Optional[str] = None
    reddit_secret: Optional[str] = None
    reddit_token_url: str = Field(default="https://www.reddit.com/api/v1/access_token")

    tiktok_client_id: Optional[str] = None
    tiktok_secret: Optional[str] = None
    tiktok_token_url: str = Field(default="https://open.tiktokapis.com/v2/oauth/token/")

    facebook_client_id: Optional[str] = None
    facebook_secret: Optional[str] = None
    model_config = SettingsConfigDict(
        env_file=find_dotenv(), env_file_encoding="utf-8", extra="ignore"
    )


class SocialMediaApi:
    """
    Social Media API client for interacting with the social media APIs.

    Attributes:
    -----------
    config: SocialMediaConfig
        configuration settings

    Methods:
    --------
    exchange_oauth_code():
        Exchange oauth code for specific access_token / refresh token depending on the platform
    authenticate():
        Authenticate with Social media using Client Credentials Flow.
    ensure_token():
        Ensure that the access token is valid. Authenticate if necessary.
    close():
        Close the HTTP client session
    """

    def __init__(self) -> None:
        self.config = SocialMediaConfig()
        self.client = httpx.AsyncClient(timeout=30.0)
        logger.info("Social Media API client initialized.")

    async def fb_exchange_to_long_lived(self, short_lived_token: str) -> dict:
        """Exchange regular access token with a long-lived (~60 days)."""
        params = {
            "grant_type": "fb_exchange_token",
            "client_id": self.config.facebook_client_id,
            "client_secret": self.config.facebook_secret,
            "fb_exchange_token": short_lived_token,
        }
        r = await self.client.get(
            "https://graph.facebook.com/v23.0/oauth/access_token", params=params
        )
        r.raise_for_status()
        return r.json()

    def missing_or_zero_expires(self, token_data: dict) -> bool:
        try:
            return int(token_data.get("expires_in", 0)) == 0
        except (ValueError, TypeError):
            return True

    async def fb_debug_token(self, user_token: str) -> dict:
        params = {
            "input_token": user_token,
            "access_token": f"{self.config.facebook_client_id}|{self.config.facebook_secret}",
        }
        r = await self.client.get(
            "https://graph.facebook.com/v23.0/debug_token", params=params
        )
        r.raise_for_status()
        data = r.json().get("data", {})
        return data

    async def exchange_oauth_code(
        self,
        code: str,
        platform: Platform,
        redirect_uri: str,
        code_verifier: Optional[str] = None,
    ) -> dict:
        """
        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 (access_token, refresh_token, etc.)
        """
        with span(logger, "exchange_oauth_code", {"platform": platform}):
            logger.info(f"Exchanging OAuth code for {platform}")

            if platform == Platform.YOUTUBE:
                client_id = self.config.youtube_client_id
                client_secret = self.config.youtube_secret
                token_url = self.config.youtube_token_url
                data = {
                    "client_id": client_id,
                    "client_secret": client_secret,
                    "code": code,
                    "grant_type": "authorization_code",
                    "redirect_uri": redirect_uri,
                }

                data = urllib.parse.urlencode(data)
                headers = {"Content-Type": "application/x-www-form-urlencoded"}
            elif platform == Platform.TWITTER:
                client_id = self.config.twitter_client_id
                client_secret = self.config.twitter_secret
                token_url = self.config.twitter_token_url

                data = {
                    "grant_type": "authorization_code",
                    "code": code,
                    "redirect_uri": redirect_uri,
                    "code_verifier": code_verifier,
                    "client_id": client_id,
                }

                auth = base64.b64encode(
                    f"{client_id}:{client_secret}".encode()
                ).decode()

                headers = {
                    "Authorization": f"Basic {auth}",
                    "Content-Type": "application/x-www-form-urlencoded",
                }
            elif platform == Platform.REDDIT:
                client_id = self.config.reddit_client_id
                client_secret = self.config.reddit_secret
                token_url = self.config.reddit_token_url

                data = {
                    "grant_type": "authorization_code",
                    "code": code,
                    "redirect_uri": redirect_uri,
                }

                auth = base64.b64encode(
                    f"{client_id}:{client_secret}".encode()
                ).decode()

                headers = {
                    "Authorization": f"Basic {auth}",
                    "Content-Type": "application/x-www-form-urlencoded",
                    "User-Agent": "tl-dr.tv",
                }
            elif platform == Platform.TIKTOK:
                client_key = self.config.tiktok_client_id
                client_secret = self.config.tiktok_secret
                token_url = self.config.tiktok_token_url

                data = {
                    "client_key": client_key,
                    "client_secret": client_secret,
                    "code": code,
                    "grant_type": "authorization_code",
                    "redirect_uri": redirect_uri,
                }

                headers = {"Content-Type": "application/x-www-form-urlencoded"}

                data = urllib.parse.urlencode(data)
            elif platform in [Platform.FACEBOOK, Platform.INSTAGRAM]:
                token_url = "https://graph.facebook.com/v23.0/oauth/access_token"

                data = {
                    "client_id": self.config.facebook_client_id,
                    "redirect_uri": redirect_uri,
                    "client_secret": self.config.facebook_secret,
                    "code": code,
                }
                data = urllib.parse.urlencode(data)
                headers = {"Content-Type": "application/x-www-form-urlencoded"}
            else:
                raise NotImplementedError(
                    f"OAuth exchange not implemented for {platform}"
                )

            try:
                response = await self.client.post(token_url, data=data, headers=headers)
                response.raise_for_status()
                token_data = response.json()
                logger.info(f"OAuth code exchanged successfully for {platform}")
                if platform == Platform.INSTAGRAM and self.missing_or_zero_expires(
                    token_data
                ):
                    logger.info(
                        "exchanging regular access token for long-lived access token"
                    )
                    try:
                        ll = await self.fb_exchange_to_long_lived(
                            token_data["access_token"]
                        )
                        token_data = {
                            **token_data,
                            "access_token": ll.get(
                                "access_token", token_data["access_token"]
                            ),
                            "token_type": ll.get(
                                "token_type", token_data.get("token_type", "bearer")
                            ),
                            "expires_in": ll.get("expires_in", 0),
                        }
                        if int(token_data.get("expires_in", 0)) == 0:
                            dbg = await self.fb_debug_token(token_data["access_token"])
                            expires_at = dbg.get("data_access_expires_at")
                            if expires_at:
                                exp_dt = datetime.fromtimestamp(
                                    expires_at, tz=timezone.utc
                                )
                                now = datetime.now(timezone.utc)
                                token_data["expires_in"] = max(
                                    0, int((exp_dt - now).total_seconds())
                                )

                        logger.info("Upgraded Instagram user token to long-lived.")
                    except httpx.HTTPError as e:
                        logger.warning(
                            f"Failed to upgrade to long-lived token: {e}. Using short-lived token."
                        )

                return token_data
            except httpx.HTTPStatusError as e:
                logger.error(
                    f"Failed to exchange code ({platform}) — {e.response.status_code}: {e}"
                )
                raise
            except httpx.HTTPError as e:
                logger.error(f"HTTP error during OAuth exchange for {platform}: {e}")
                raise
            except Exception as e:
                logger.error(
                    f"Unexpected error during OAuth exchange for {platform}: {e}"
                )
                raise

    LONG_LIVED_THRESHOLD_SECONDS = 45 * 24 * 60 * 60
    TOL_SHORT = timedelta(seconds=60)
    TOL_LONG = timedelta(days=30)

    def ensure_token(self, token: UserAccessToken) -> bool:
        """
        Ensure that the access token is still valid.
        Returns True if valid, False if expired or about to expire (within tolerance).
        """

        if token.expires_in == 0:
            return False

        now = datetime.now(timezone.utc)
        is_long_lived = token.expires_in >= SocialMediaApi.LONG_LIVED_THRESHOLD_SECONDS

        tolerance = (
            SocialMediaApi.TOL_LONG if is_long_lived else SocialMediaApi.TOL_SHORT
        )

        expiration_time = token.updated_at + timedelta(seconds=token.expires_in)

        expiration_with_tolerance = expiration_time - tolerance

        return now < expiration_with_tolerance

    async def refresh_access_token(
        self, token: UserAccessToken
    ) -> UserAccessTokenUpdate:
        """
        Refresh the access token using the refresh token.
        """

        YOUTUBE_TOKEN_URL = "https://oauth2.googleapis.com/token"
        REDDIT_TOKEN_URL = "https://www.reddit.com/api/v1/access_token"

        if not token.refresh_token and token.platform not in (
            Platform.FACEBOOK,
            Platform.INSTAGRAM,
        ):
            raise RuntimeError(
                f"No refresh token available for platform {token.platform}"
            )

        if token.platform == Platform.YOUTUBE:
            client_id = self.config.youtube_client_id
            client_secret = self.config.youtube_secret

            data = {
                "client_id": client_id,
                "client_secret": client_secret,
                "refresh_token": token.refresh_token,
                "grant_type": "refresh_token",
            }

            async with httpx.AsyncClient(timeout=10) as client:
                resp = await client.post(YOUTUBE_TOKEN_URL, data=data)
                resp.raise_for_status()
                payload = resp.json()

            try:
                return UserAccessTokenUpdate(
                    access_token=payload["access_token"],
                    expires_in=payload.get("expires_in"),
                    token_type=payload.get("token_type"),
                    refresh_token=payload.get("refresh_token") or token.refresh_token,
                    scope=payload.get("scope", "").split()
                    if payload.get("scope")
                    else None,
                    platform=token.platform,
                    updated_at=datetime.utcnow(),
                )
            except KeyError as e:
                raise RuntimeError(f"Missing expected field in refresh response: {e}")
            except ValidationError as e:
                raise RuntimeError(f"Invalid token data format: {e}")
        elif token.platform == Platform.TIKTOK:
            url = "https://open-api.tiktok.com/oauth/refresh_token/"
            data = {
                "client_key": self.config.tiktok_client_id,
                "grant_type": "refresh_token",
                "refresh_token": token.refresh_token,
            }

            async with httpx.AsyncClient(timeout=10) as client:
                resp = await client.post(url, json=data)
                resp.raise_for_status()
                payload = resp.json().get("data", {})

            return UserAccessTokenUpdate(
                access_token=payload.get("access_token"),
                expires_in=payload.get("expires_in"),
                refresh_token=payload.get("refresh_token") or token.refresh_token,
                token_type=payload.get("token_type"),
                scope=payload.get("scope").split() if payload.get("scope") else None,
                platform=token.platform,
                updated_at=datetime.utcnow(),
            )
        elif token.platform in (Platform.FACEBOOK, Platform.INSTAGRAM):
            ll = await self.fb_exchange_to_long_lived(token.access_token)

            access_token = ll.get("access_token", token.access_token)
            token_type = ll.get("token_type", "bearer")
            expires_in = ll.get("expires_in", 0)
            if int(expires_in or 0) == 0:
                dbg = await self.fb_debug_token(access_token)
                expires_at = dbg.get("data_access_expires_at")
                if expires_at:
                    exp_dt = datetime.fromtimestamp(expires_at, tz=timezone.utc)
                    now = datetime.now(timezone.utc)
                    expires_in = max(0, int((exp_dt - now).total_seconds()))
            return UserAccessTokenUpdate(
                access_token=access_token,
                expires_in=expires_in,
                token_type=token_type,
                refresh_token=token.refresh_token,
                scope=token.scope,
                platform=token.platform,
                updated_at=datetime.utcnow(),
            )
        elif token.platform == Platform.REDDIT:
            client_id = self.config.reddit_client_id
            client_secret = self.config.reddit_secret

            data = {
                "grant_type": "refresh_token",
                "refresh_token": token.refresh_token,
            }

            async with httpx.AsyncClient(timeout=10) as client:
                resp = await client.post(
                    REDDIT_TOKEN_URL,
                    data=data,
                    auth=(client_id, client_secret),
                )
                resp.raise_for_status()
                payload = resp.json()
                return UserAccessTokenUpdate(
                    access_token=payload["access_token"],
                    expires_in=payload.get("expires_in"),
                    token_type=payload.get("token_type"),
                    refresh_token=payload.get("refresh_token") or token.refresh_token,
                    scope=payload.get("scope", "").split()
                    if payload.get("scope")
                    else None,
                    platform=token.platform,
                    updated_at=datetime.utcnow(),
                )
        else:
            raise NotImplementedError(
                f"Refresh flow not implemented for platform {token.platform}"
            )

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