# tiktok_client.py
import os
import requests
import urllib.parse
from typing import Optional, Literal

from config import (
    TIKTOK_CLIENT_KEY,
    TIKTOK_CLIENT_SECRET,
    TIKTOK_REDIRECT_URI,
    TIKTOK_SCOPES,
    TIKTOK_PRODUCTION,
)

TIKTOK_OAUTH_AUTHORIZE_URL = "https://www.tiktok.com/v2/auth/authorize/"
TIKTOK_OAUTH_TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/"

# Draft (inbox) upload – requires video.upload
INBOX_INIT_URL = "https://open.tiktokapis.com/v2/post/publish/inbox/video/init/"

# Direct post (publish) – requires video.publish + video.upload (for FILE_UPLOAD)
# For PULL_FROM_URL, it still uses this init endpoint but no PUT upload.
DIRECT_POST_INIT_URL = "https://open.tiktokapis.com/v2/post/publish/video/init/"

# Required: creator info and status fetch
CREATOR_INFO_URL = "https://open.tiktokapis.com/v2/post/publish/creator_info/query/"
STATUS_FETCH_URL = "https://open.tiktokapis.com/v2/post/publish/status/fetch/"


# ----------------------------
#  OAuth helpers
# ----------------------------

def build_authorize_url(state: str) -> str:
    params = {
        "client_key": TIKTOK_CLIENT_KEY,
        "response_type": "code",
        "scope": TIKTOK_SCOPES,
        "redirect_uri": TIKTOK_REDIRECT_URI,
        "state": state,
    }
    return TIKTOK_OAUTH_AUTHORIZE_URL + "?" + urllib.parse.urlencode(params)


def exchange_code_for_token(code: str) -> dict:
    payload = {
        "client_key": TIKTOK_CLIENT_KEY,
        "client_secret": TIKTOK_CLIENT_SECRET,
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": TIKTOK_REDIRECT_URI,
    }
    resp = requests.post(TIKTOK_OAUTH_TOKEN_URL, data=payload, timeout=30)
    resp.raise_for_status()
    return resp.json()


# ----------------------------
#  Mandatory audit endpoints
# ----------------------------

def query_creator_info(access_token: str) -> dict:
    """
    Mandatory for audit: retrieve latest creator info right before rendering Post-to-TikTok page.

    Returns data dict with fields such as:
      - creator_nickname
      - privacy_level_options (list)
      - comment_disabled / duet_disabled / stitch_disabled (bool)
      - max_video_post_duration_sec (int)
      - may also include "can_post" flags depending on account state
    """
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=UTF-8",
    }
    resp = requests.post(CREATOR_INFO_URL, headers=headers, json={}, timeout=30)
    resp.raise_for_status()
    payload = resp.json()

    err = payload.get("error", {})
    if err.get("code") != "ok":
        raise RuntimeError(f"creator_info error: {err}")

    return payload.get("data", {})


def fetch_post_status(access_token: str, publish_id: str) -> dict:
    """
    Poll post publish status so users can see progress.
    """
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=UTF-8",
    }
    resp = requests.post(
        STATUS_FETCH_URL, headers=headers, json={"publish_id": publish_id}, timeout=30
    )
    resp.raise_for_status()
    payload = resp.json()

    err = payload.get("error", {})
    if err.get("code") != "ok":
        raise RuntimeError(f"status_fetch error: {err}")

    return payload.get("data", {})


# ============================
#  MODE 1: DRAFT (video.upload) - FILE_UPLOAD
# ============================

def _upload_video_as_draft(file_path: str, access_token: str) -> dict:
    """
    Upload as draft (inbox) using video.upload:
      1) INIT -> /post/publish/inbox/video/init/
      2) PUT  -> upload_url with full file (Content-Range)
    """
    file_size = os.path.getsize(file_path)

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=UTF-8",
    }
    body = {
        "source_info": {
            "source": "FILE_UPLOAD",
            "video_size": file_size,
            "chunk_size": file_size,
            "total_chunk_count": 1,
        }
    }

    init_resp = requests.post(INBOX_INIT_URL, headers=headers, json=body, timeout=60)
    init_resp.raise_for_status()
    init_data = init_resp.json()

    error = init_data.get("error", {})
    if error.get("code") != "ok":
        raise RuntimeError(f"TikTok inbox init error: {error}")

    data = init_data.get("data", {})
    upload_url = data.get("upload_url")
    if not upload_url:
        raise RuntimeError(f"No upload_url in TikTok inbox response: {init_data}")

    # PUT file with Content-Range
    end_byte = file_size - 1
    put_headers = {
        "Content-Type": "video/mp4",
        "Content-Range": f"bytes 0-{end_byte}/{file_size}",
    }

    with open(file_path, "rb") as f:
        put_resp = requests.put(upload_url, headers=put_headers, data=f, timeout=300)
    put_resp.raise_for_status()

    return init_data


# ============================
#  MODE 2A: DIRECT POST - PULL_FROM_URL (recommended for server-side storage)
# ============================

def _publish_video_from_url(
    *,
    access_token: str,
    video_url: str,
    caption: str,
    privacy_level: str,
    disable_comment: bool,
    disable_duet: bool,
    disable_stitch: bool,
    brand_content_toggle: bool,
    brand_organic_toggle: bool,
    is_aigc: bool = True,
    video_cover_timestamp_ms: Optional[int] = None,
) -> dict:
    """
    Direct Post using PULL_FROM_URL (TikTok pulls the video from your URL).
    This is the correct mode when the MP4 already exists on your server/storage.

    Important:
      - video_url must be reachable by TikTok servers.
      - Your domain/prefix must be allowed in TT4D "Manage URL properties".
      - privacy_level must be explicitly selected by the user from privacy_level_options.
    """
    if not video_url or not isinstance(video_url, str):
        raise ValueError("video_url is required for PULL_FROM_URL")

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=UTF-8",
    }

    post_info = {
        "privacy_level": privacy_level,
        "title": caption or "",
        "disable_comment": disable_comment,
        "disable_duet": disable_duet,
        "disable_stitch": disable_stitch,
        "brand_content_toggle": brand_content_toggle,
        "brand_organic_toggle": brand_organic_toggle,
        "is_aigc": is_aigc,
    }
    if video_cover_timestamp_ms is not None:
        post_info["video_cover_timestamp_ms"] = int(video_cover_timestamp_ms)

    body = {
        "source_info": {
            "source": "PULL_FROM_URL",
            "video_url": video_url,
        },
        "post_info": post_info,
    }

    init_resp = requests.post(DIRECT_POST_INIT_URL, headers=headers, json=body, timeout=60)
    if init_resp.status_code >= 400:
        raise RuntimeError(f"Direct init failed {init_resp.status_code}: {init_resp.text}")
    init_resp.raise_for_status()
    init_data = init_resp.json()

    error = init_data.get("error", {})
    if error.get("code") != "ok":
        raise RuntimeError(f"TikTok direct init (PULL_FROM_URL) error: {error}")

    # For PULL_FROM_URL, TikTok usually returns publish_id but NO upload_url.
    return init_data


# ============================
#  MODE 2B: DIRECT POST - FILE_UPLOAD (fallback / tests)
# ============================

def _upload_and_publish_video_file_upload(
    *,
    file_path: str,
    access_token: str,
    caption: str,
    privacy_level: str,
    disable_comment: bool,
    disable_duet: bool,
    disable_stitch: bool,
    brand_content_toggle: bool,
    brand_organic_toggle: bool,
    is_aigc: bool = True,
    video_cover_timestamp_ms: Optional[int] = None,
) -> dict:
    """
    Direct Post (FILE_UPLOAD):
      1) INIT -> /v2/post/publish/video/init/ with source_info + post_info
      2) PUT  -> upload_url with full file (Content-Range)
      3) TikTok publishes based on post_info

    NOTE: Not recommended when the video is already on your server-side storage.
    Use PULL_FROM_URL for audit compliance in that scenario.
    """
    file_size = os.path.getsize(file_path)

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=UTF-8",
    }

    post_info = {
        "privacy_level": privacy_level,
        "title": caption or "",
        "disable_comment": disable_comment,
        "disable_duet": disable_duet,
        "disable_stitch": disable_stitch,
        "brand_content_toggle": brand_content_toggle,
        "brand_organic_toggle": brand_organic_toggle,
        "is_aigc": is_aigc,
    }
    if video_cover_timestamp_ms is not None:
        post_info["video_cover_timestamp_ms"] = int(video_cover_timestamp_ms)

    body = {
        "source_info": {
            "source": "FILE_UPLOAD",
            "video_size": file_size,
            "chunk_size": file_size,
            "total_chunk_count": 1,
        },
        "post_info": post_info,
    }

    init_resp = requests.post(DIRECT_POST_INIT_URL, headers=headers, json=body, timeout=60)
    if init_resp.status_code >= 400:
        raise RuntimeError(f"Direct init failed {init_resp.status_code}: {init_resp.text}")
    init_resp.raise_for_status()
    init_data = init_resp.json()

    error = init_data.get("error", {})
    if error.get("code") != "ok":
        raise RuntimeError(f"TikTok direct init (FILE_UPLOAD) error: {error}")

    data = init_data.get("data", {})
    upload_url = data.get("upload_url")
    if not upload_url:
        raise RuntimeError(f"No upload_url in TikTok direct response: {init_data}")

    # PUT file with Content-Range
    end_byte = file_size - 1
    put_headers = {
        "Content-Type": "video/mp4",
        "Content-Range": f"bytes 0-{end_byte}/{file_size}",
    }

    with open(file_path, "rb") as f:
        put_resp = requests.put(upload_url, headers=put_headers, data=f, timeout=300)
    put_resp.raise_for_status()

    return init_data


# ----------------------------
#  Public wrappers (stable API for your Flask app)
# ----------------------------

def upload_video_draft(file_path: str, access_token: str) -> dict:
    return _upload_video_as_draft(file_path=file_path, access_token=access_token)


PublishMode = Literal["PULL_FROM_URL", "FILE_UPLOAD"]

def upload_video_direct_post(
    *,
    access_token: str,
    caption: str,
    privacy_level: str,
    disable_comment: bool,
    disable_duet: bool,
    disable_stitch: bool,
    brand_content_toggle: bool,
    brand_organic_toggle: bool,
    is_aigc: bool = True,
    video_cover_timestamp_ms: Optional[int] = None,
    mode: PublishMode = "PULL_FROM_URL",
    # Required for PULL_FROM_URL:
    video_url: Optional[str] = None,
    # Required for FILE_UPLOAD:
    file_path: Optional[str] = None,
) -> dict:
    """
    Unified entrypoint:
      - mode="PULL_FROM_URL": requires video_url (recommended for audit if video is on your server)
      - mode="FILE_UPLOAD": requires file_path (fallback/tests)
    """
    if mode == "PULL_FROM_URL":
        if not video_url:
            raise ValueError("video_url is required when mode='PULL_FROM_URL'")
        return _publish_video_from_url(
            access_token=access_token,
            video_url=video_url,
            caption=caption,
            privacy_level=privacy_level,
            disable_comment=disable_comment,
            disable_duet=disable_duet,
            disable_stitch=disable_stitch,
            brand_content_toggle=brand_content_toggle,
            brand_organic_toggle=brand_organic_toggle,
            is_aigc=is_aigc,
            video_cover_timestamp_ms=video_cover_timestamp_ms,
        )

    if mode == "FILE_UPLOAD":
        if not file_path:
            raise ValueError("file_path is required when mode='FILE_UPLOAD'")
        return _upload_and_publish_video_file_upload(
            file_path=file_path,
            access_token=access_token,
            caption=caption,
            privacy_level=privacy_level,
            disable_comment=disable_comment,
            disable_duet=disable_duet,
            disable_stitch=disable_stitch,
            brand_content_toggle=brand_content_toggle,
            brand_organic_toggle=brand_organic_toggle,
            is_aigc=is_aigc,
            video_cover_timestamp_ms=video_cover_timestamp_ms,
        )

    raise ValueError(f"Unsupported mode: {mode}")
