//! TikTok Content Posting API Integration
//!
//! Provides video upload, publishing, and user management capabilities.
//! Supports OAuth 2.0 authentication flow for TikTok Login Kit.

use crate::channels::{
    ChannelAccount, ChannelCredentials, ChannelError, ChannelProvider, ChannelType, PostContent,
    PostResult,
};
use serde::{Deserialize, Serialize};

/// TikTok API provider for video uploads and content posting
pub struct TikTokProvider {
    client: reqwest::Client,
    api_base_url: String,
    oauth_base_url: String,
}

impl TikTokProvider {
    pub fn new() -> Self {
        Self {
            client: reqwest::Client::new(),
            api_base_url: "https://open.tiktokapis.com/v2".to_string(),
            oauth_base_url: "https://open.tiktokapis.com/v2/oauth".to_string(),
        }
    }

    /// Initialize a video upload (Direct Post)
    /// Returns upload URL and publish_id for tracking
    pub async fn init_video_upload(
        &self,
        access_token: &str,
        request: &VideoUploadRequest,
    ) -> Result<VideoUploadInit, ChannelError> {
        let url = format!("{}/post/publish/video/init/", self.api_base_url);

        let post_info = serde_json::json!({
            "title": request.title,
            "privacy_level": request.privacy_level,
            "disable_duet": request.disable_duet.unwrap_or(false),
            "disable_comment": request.disable_comment.unwrap_or(false),
            "disable_stitch": request.disable_stitch.unwrap_or(false),
            "video_cover_timestamp_ms": request.video_cover_timestamp_ms.unwrap_or(0),
            "brand_content_toggle": request.brand_content_toggle.unwrap_or(false),
            "brand_organic_toggle": request.brand_organic_toggle.unwrap_or(false)
        });

        let source_info = if let Some(url) = &request.video_url {
            serde_json::json!({
                "source": "PULL_FROM_URL",
                "video_url": url
            })
        } else {
            serde_json::json!({
                "source": "FILE_UPLOAD",
                "video_size": request.video_size.unwrap_or(0),
                "chunk_size": request.chunk_size.unwrap_or(10_000_000),
                "total_chunk_count": request.total_chunk_count.unwrap_or(1)
            })
        };

        let request_body = serde_json::json!({
            "post_info": post_info,
            "source_info": source_info
        });

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .header("Content-Type", "application/json; charset=UTF-8")
            .json(&request_body)
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<VideoUploadInit> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response.data.ok_or_else(|| ChannelError::ApiError {
            code: api_response.error.as_ref().map(|e| e.code.clone()),
            message: api_response
                .error
                .map(|e| e.message)
                .unwrap_or_else(|| "Unknown error".to_string()),
        })
    }

    /// Upload video chunk (for FILE_UPLOAD source)
    pub async fn upload_video_chunk(
        &self,
        upload_url: &str,
        chunk_data: &[u8],
        chunk_index: u32,
        total_chunks: u32,
    ) -> Result<(), ChannelError> {
        let content_range = if total_chunks == 1 {
            format!("bytes 0-{}/{}", chunk_data.len() - 1, chunk_data.len())
        } else {
            let start = chunk_index as usize * chunk_data.len();
            let end = start + chunk_data.len() - 1;
            let total = total_chunks as usize * chunk_data.len();
            format!("bytes {}-{}/{}", start, end, total)
        };

        let response = self
            .client
            .put(upload_url)
            .header("Content-Type", "video/mp4")
            .header("Content-Length", chunk_data.len().to_string())
            .header("Content-Range", content_range)
            .body(chunk_data.to_vec())
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        Ok(())
    }

    /// Check the status of a video upload/publish
    pub async fn get_publish_status(
        &self,
        access_token: &str,
        publish_id: &str,
    ) -> Result<PublishStatus, ChannelError> {
        let url = format!("{}/post/publish/status/fetch/", self.api_base_url);

        let request_body = serde_json::json!({
            "publish_id": publish_id
        });

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .header("Content-Type", "application/json; charset=UTF-8")
            .json(&request_body)
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<PublishStatus> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response.data.ok_or_else(|| ChannelError::ApiError {
            code: api_response.error.as_ref().map(|e| e.code.clone()),
            message: api_response
                .error
                .map(|e| e.message)
                .unwrap_or_else(|| "Unknown error".to_string()),
        })
    }

    /// Initialize a photo post
    pub async fn init_photo_post(
        &self,
        access_token: &str,
        request: &PhotoPostRequest,
    ) -> Result<PhotoUploadInit, ChannelError> {
        let url = format!("{}/post/publish/content/init/", self.api_base_url);

        let request_body = serde_json::json!({
            "post_info": {
                "title": request.title,
                "description": request.description,
                "privacy_level": request.privacy_level,
                "disable_comment": request.disable_comment.unwrap_or(false),
                "auto_add_music": request.auto_add_music.unwrap_or(true)
            },
            "source_info": {
                "source": "PULL_FROM_URL",
                "photo_cover_index": request.photo_cover_index.unwrap_or(0),
                "photo_images": request.photo_urls
            },
            "post_mode": "DIRECT_POST",
            "media_type": "PHOTO"
        });

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .header("Content-Type", "application/json; charset=UTF-8")
            .json(&request_body)
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<PhotoUploadInit> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response.data.ok_or_else(|| ChannelError::ApiError {
            code: api_response.error.as_ref().map(|e| e.code.clone()),
            message: api_response
                .error
                .map(|e| e.message)
                .unwrap_or_else(|| "Unknown error".to_string()),
        })
    }

    /// Get user info (requires user.info.basic or user.info.profile scope)
    pub async fn get_user_info(
        &self,
        access_token: &str,
        fields: &[&str],
    ) -> Result<TikTokUser, ChannelError> {
        let fields_param = fields.join(",");
        let url = format!("{}/user/info/", self.api_base_url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .query(&[("fields", fields_param)])
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<UserInfoData> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response
            .data
            .and_then(|d| d.user)
            .ok_or_else(|| ChannelError::ApiError {
                code: api_response.error.as_ref().map(|e| e.code.clone()),
                message: api_response
                    .error
                    .map(|e| e.message)
                    .unwrap_or_else(|| "User not found".to_string()),
            })
    }

    /// List user videos (requires video.list scope)
    pub async fn list_videos(
        &self,
        access_token: &str,
        options: &VideoListOptions,
    ) -> Result<VideoListResponse, ChannelError> {
        let url = format!("{}/video/list/", self.api_base_url);

        let fields = options.fields.as_deref().unwrap_or(
            "id,create_time,cover_image_url,share_url,video_description,duration,title",
        );

        let mut request_body = serde_json::json!({
            "max_count": options.max_count.unwrap_or(20)
        });

        if let Some(cursor) = &options.cursor {
            request_body["cursor"] = serde_json::json!(cursor);
        }

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .header("Content-Type", "application/json; charset=UTF-8")
            .query(&[("fields", fields)])
            .json(&request_body)
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<VideoListResponse> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response.data.ok_or_else(|| ChannelError::ApiError {
            code: api_response.error.as_ref().map(|e| e.code.clone()),
            message: api_response
                .error
                .map(|e| e.message)
                .unwrap_or_else(|| "Failed to list videos".to_string()),
        })
    }

    /// Query specific videos by IDs (requires video.list scope)
    pub async fn query_videos(
        &self,
        access_token: &str,
        video_ids: &[String],
        fields: Option<&str>,
    ) -> Result<Vec<TikTokVideo>, ChannelError> {
        let url = format!("{}/video/query/", self.api_base_url);

        let fields_param = fields.unwrap_or(
            "id,create_time,cover_image_url,share_url,video_description,duration,title,like_count,comment_count,share_count,view_count",
        );

        let request_body = serde_json::json!({
            "filters": {
                "video_ids": video_ids
            }
        });

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .header("Content-Type", "application/json; charset=UTF-8")
            .query(&[("fields", fields_param)])
            .json(&request_body)
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<VideoQueryResponse> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response
            .data
            .map(|d| d.videos)
            .ok_or_else(|| ChannelError::ApiError {
                code: api_response.error.as_ref().map(|e| e.code.clone()),
                message: api_response
                    .error
                    .map(|e| e.message)
                    .unwrap_or_else(|| "Failed to query videos".to_string()),
            })
    }

    /// Get creator info for content posting (requires video.publish scope)
    pub async fn get_creator_info(
        &self,
        access_token: &str,
    ) -> Result<CreatorInfo, ChannelError> {
        let url = format!("{}/post/publish/creator_info/", self.api_base_url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", access_token))
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        let api_response: TikTokApiResponse<CreatorInfo> =
            response.json().await.map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })?;

        api_response.data.ok_or_else(|| ChannelError::ApiError {
            code: api_response.error.as_ref().map(|e| e.code.clone()),
            message: api_response
                .error
                .map(|e| e.message)
                .unwrap_or_else(|| "Failed to get creator info".to_string()),
        })
    }

    /// Refresh OAuth access token
    pub async fn refresh_oauth_token(
        &self,
        client_key: &str,
        client_secret: &str,
        refresh_token: &str,
    ) -> Result<OAuthTokenResponse, ChannelError> {
        let url = format!("{}/token/", self.oauth_base_url);

        let response = self
            .client
            .post(&url)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .form(&[
                ("client_key", client_key),
                ("client_secret", client_secret),
                ("grant_type", "refresh_token"),
                ("refresh_token", refresh_token),
            ])
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        response
            .json::<OAuthTokenResponse>()
            .await
            .map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })
    }

    /// Revoke OAuth access token
    pub async fn revoke_token(
        &self,
        client_key: &str,
        client_secret: &str,
        access_token: &str,
    ) -> Result<(), ChannelError> {
        let url = format!("{}/revoke/", self.oauth_base_url);

        let response = self
            .client
            .post(&url)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .form(&[
                ("client_key", client_key),
                ("client_secret", client_secret),
                ("token", access_token),
            ])
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        Ok(())
    }

    /// Generate OAuth authorization URL
    pub fn get_authorization_url(
        &self,
        client_key: &str,
        redirect_uri: &str,
        scope: &str,
        state: &str,
    ) -> String {
        format!(
            "https://www.tiktok.com/v2/auth/authorize/?client_key={}&scope={}&response_type=code&redirect_uri={}&state={}",
            client_key,
            urlencoding::encode(scope),
            urlencoding::encode(redirect_uri),
            urlencoding::encode(state)
        )
    }

    /// Exchange authorization code for access token
    pub async fn exchange_code_for_token(
        &self,
        client_key: &str,
        client_secret: &str,
        code: &str,
        redirect_uri: &str,
    ) -> Result<OAuthTokenResponse, ChannelError> {
        let url = format!("{}/token/", self.oauth_base_url);

        let response = self
            .client
            .post(&url)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .form(&[
                ("client_key", client_key),
                ("client_secret", client_secret),
                ("code", code),
                ("grant_type", "authorization_code"),
                ("redirect_uri", redirect_uri),
            ])
            .send()
            .await
            .map_err(|e| ChannelError::NetworkError(e.to_string()))?;

        if !response.status().is_success() {
            return Err(self.parse_error_response(response).await);
        }

        response
            .json::<OAuthTokenResponse>()
            .await
            .map_err(|e| ChannelError::ApiError {
                code: None,
                message: e.to_string(),
            })
    }

    async fn parse_error_response(&self, response: reqwest::Response) -> ChannelError {
        let status = response.status();

        if status.as_u16() == 401 {
            return ChannelError::AuthenticationFailed("Invalid or expired token".to_string());
        }

        if status.as_u16() == 429 {
            let retry_after = response
                .headers()
                .get("x-ratelimit-reset")
                .and_then(|v| v.to_str().ok())
                .and_then(|s| s.parse().ok());
            return ChannelError::RateLimited { retry_after };
        }

        let error_text = response.text().await.unwrap_or_default();

        if let Ok(api_response) = serde_json::from_str::<TikTokApiResponse<()>>(&error_text) {
            if let Some(error) = api_response.error {
                return ChannelError::ApiError {
                    code: Some(error.code),
                    message: error.message,
                };
            }
        }

        ChannelError::ApiError {
            code: Some(status.to_string()),
            message: error_text,
        }
    }
}

impl Default for TikTokProvider {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait::async_trait]
impl ChannelProvider for TikTokProvider {
    fn channel_type(&self) -> ChannelType {
        ChannelType::TikTok
    }

    fn max_text_length(&self) -> usize {
        2200 // Max caption length
    }

    fn supports_images(&self) -> bool {
        true // Photo mode
    }

    fn supports_video(&self) -> bool {
        true
    }

    fn supports_links(&self) -> bool {
        false // Links not clickable in TikTok captions
    }

    async fn post(
        &self,
        account: &ChannelAccount,
        content: &PostContent,
    ) -> Result<PostResult, ChannelError> {
        let access_token = match &account.credentials {
            ChannelCredentials::OAuth { access_token, .. } => access_token.clone(),
            _ => {
                return Err(ChannelError::AuthenticationFailed(
                    "OAuth credentials required for TikTok".to_string(),
                ))
            }
        };

        let text = content.text.as_deref().unwrap_or("");

        if text.len() > self.max_text_length() {
            return Err(ChannelError::ContentTooLong {
                max_length: self.max_text_length(),
                actual_length: text.len(),
            });
        }

        // Determine privacy level from settings or default
        let privacy_level = account
            .settings
            .custom
            .get("default_privacy")
            .and_then(|v| v.as_str())
            .unwrap_or("SELF_ONLY")
            .to_string();

        // Check if we're posting a video or photos
        if let Some(video_url) = &content.video_url {
            // Video post via URL
            let request = VideoUploadRequest {
                title: text.to_string(),
                privacy_level,
                video_url: Some(video_url.clone()),
                disable_duet: Some(false),
                disable_comment: Some(false),
                disable_stitch: Some(false),
                video_cover_timestamp_ms: None,
                brand_content_toggle: None,
                brand_organic_toggle: None,
                video_size: None,
                chunk_size: None,
                total_chunk_count: None,
            };

            let init_result = self.init_video_upload(&access_token, &request).await?;

            Ok(PostResult::success(
                ChannelType::TikTok,
                init_result.publish_id,
                None, // URL not available until processing complete
            ))
        } else if !content.image_urls.is_empty() {
            // Photo post
            let request = PhotoPostRequest {
                title: text.to_string(),
                description: content
                    .metadata
                    .get("description")
                    .and_then(|v| v.as_str())
                    .map(String::from),
                privacy_level,
                photo_urls: content.image_urls.clone(),
                photo_cover_index: Some(0),
                disable_comment: Some(false),
                auto_add_music: Some(true),
            };

            let init_result = self.init_photo_post(&access_token, &request).await?;

            Ok(PostResult::success(
                ChannelType::TikTok,
                init_result.publish_id,
                None,
            ))
        } else {
            Err(ChannelError::ApiError {
                code: None,
                message: "TikTok requires either a video or photos to post".to_string(),
            })
        }
    }

    async fn validate_credentials(
        &self,
        credentials: &ChannelCredentials,
    ) -> Result<bool, ChannelError> {
        match credentials {
            ChannelCredentials::OAuth { access_token, .. } => {
                match self
                    .get_user_info(access_token, &["open_id", "display_name"])
                    .await
                {
                    Ok(_) => Ok(true),
                    Err(ChannelError::AuthenticationFailed(_)) => Ok(false),
                    Err(e) => Err(e),
                }
            }
            _ => Ok(false),
        }
    }

    async fn refresh_token(&self, account: &mut ChannelAccount) -> Result<(), ChannelError> {
        let (refresh_token, client_key, client_secret) = match &account.credentials {
            ChannelCredentials::OAuth { refresh_token, .. } => {
                let refresh = refresh_token.as_ref().ok_or_else(|| {
                    ChannelError::AuthenticationFailed("No refresh token available".to_string())
                })?;
                let client_key = account
                    .settings
                    .custom
                    .get("client_key")
                    .and_then(|v| v.as_str())
                    .ok_or_else(|| {
                        ChannelError::AuthenticationFailed("Missing client_key".to_string())
                    })?;
                let client_secret = account
                    .settings
                    .custom
                    .get("client_secret")
                    .and_then(|v| v.as_str())
                    .ok_or_else(|| {
                        ChannelError::AuthenticationFailed("Missing client_secret".to_string())
                    })?;
                (
                    refresh.clone(),
                    client_key.to_string(),
                    client_secret.to_string(),
                )
            }
            _ => {
                return Err(ChannelError::AuthenticationFailed(
                    "OAuth credentials required".to_string(),
                ))
            }
        };

        let token_response = self
            .refresh_oauth_token(&client_key, &client_secret, &refresh_token)
            .await?;

        let expires_at =
            chrono::Utc::now() + chrono::Duration::seconds(token_response.expires_in as i64);

        account.credentials = ChannelCredentials::OAuth {
            access_token: token_response.access_token,
            refresh_token: Some(token_response.refresh_token),
            expires_at: Some(expires_at),
            scope: Some(token_response.scope),
        };

        Ok(())
    }
}

// ============================================================================
// Request Types
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoUploadRequest {
    pub title: String,
    pub privacy_level: String, // "PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY", "FOLLOWER_OF_CREATOR"
    pub video_url: Option<String>,
    pub disable_duet: Option<bool>,
    pub disable_comment: Option<bool>,
    pub disable_stitch: Option<bool>,
    pub video_cover_timestamp_ms: Option<u64>,
    pub brand_content_toggle: Option<bool>,
    pub brand_organic_toggle: Option<bool>,
    // For FILE_UPLOAD source
    pub video_size: Option<u64>,
    pub chunk_size: Option<u64>,
    pub total_chunk_count: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhotoPostRequest {
    pub title: String,
    pub description: Option<String>,
    pub privacy_level: String,
    pub photo_urls: Vec<String>,
    pub photo_cover_index: Option<u32>,
    pub disable_comment: Option<bool>,
    pub auto_add_music: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoListOptions {
    pub cursor: Option<i64>,
    pub max_count: Option<u32>,
    pub fields: Option<String>,
}

// ============================================================================
// Response Types
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TikTokApiResponse<T> {
    pub data: Option<T>,
    pub error: Option<TikTokError>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TikTokError {
    pub code: String,
    pub message: String,
    pub log_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoUploadInit {
    pub publish_id: String,
    pub upload_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhotoUploadInit {
    pub publish_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishStatus {
    pub status: String, // "PROCESSING_UPLOAD", "PROCESSING_DOWNLOAD", "SEND_TO_USER_INBOX", "PUBLISH_COMPLETE", "FAILED"
    #[serde(default)]
    pub fail_reason: Option<String>,
    pub publicly_available_post_id: Option<Vec<String>>,
    pub uploaded_bytes: Option<u64>,
}

impl PublishStatus {
    pub fn is_complete(&self) -> bool {
        self.status == "PUBLISH_COMPLETE"
    }

    pub fn is_failed(&self) -> bool {
        self.status == "FAILED"
    }

    pub fn is_processing(&self) -> bool {
        matches!(
            self.status.as_str(),
            "PROCESSING_UPLOAD" | "PROCESSING_DOWNLOAD" | "SEND_TO_USER_INBOX"
        )
    }

    pub fn get_video_id(&self) -> Option<&str> {
        self.publicly_available_post_id
            .as_ref()
            .and_then(|ids| ids.first())
            .map(|s| s.as_str())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfoData {
    pub user: Option<TikTokUser>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TikTokUser {
    pub open_id: String,
    pub union_id: Option<String>,
    pub display_name: Option<String>,
    pub avatar_url: Option<String>,
    pub avatar_url_100: Option<String>,
    pub avatar_large_url: Option<String>,
    pub bio_description: Option<String>,
    pub profile_deep_link: Option<String>,
    pub is_verified: Option<bool>,
    pub follower_count: Option<u64>,
    pub following_count: Option<u64>,
    pub likes_count: Option<u64>,
    pub video_count: Option<u64>,
}

impl TikTokUser {
    /// Get the user's TikTok profile URL
    pub fn profile_url(&self) -> Option<String> {
        self.profile_deep_link.clone()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoListResponse {
    pub videos: Vec<TikTokVideo>,
    pub cursor: Option<i64>,
    pub has_more: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoQueryResponse {
    pub videos: Vec<TikTokVideo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TikTokVideo {
    pub id: String,
    pub create_time: Option<i64>,
    pub cover_image_url: Option<String>,
    pub share_url: Option<String>,
    pub video_description: Option<String>,
    pub duration: Option<u32>,
    pub title: Option<String>,
    pub height: Option<u32>,
    pub width: Option<u32>,
    pub like_count: Option<u64>,
    pub comment_count: Option<u64>,
    pub share_count: Option<u64>,
    pub view_count: Option<u64>,
    pub embed_html: Option<String>,
    pub embed_link: Option<String>,
}

impl TikTokVideo {
    /// Get the video URL
    pub fn url(&self) -> Option<&str> {
        self.share_url.as_deref()
    }

    /// Get video creation time as DateTime
    pub fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
        self.create_time
            .and_then(|ts| chrono::DateTime::from_timestamp(ts, 0))
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatorInfo {
    pub creator_avatar_url: Option<String>,
    pub creator_username: Option<String>,
    pub creator_nickname: Option<String>,
    pub privacy_level_options: Option<Vec<String>>,
    pub comment_disabled: Option<bool>,
    pub duet_disabled: Option<bool>,
    pub stitch_disabled: Option<bool>,
    pub max_video_post_duration_sec: Option<u32>,
}

impl CreatorInfo {
    /// Check if public posting is available
    pub fn can_post_public(&self) -> bool {
        self.privacy_level_options
            .as_ref()
            .map(|opts| opts.contains(&"PUBLIC_TO_EVERYONE".to_string()))
            .unwrap_or(false)
    }

    /// Get max video duration in seconds
    pub fn max_video_duration(&self) -> u32 {
        self.max_video_post_duration_sec.unwrap_or(60)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthTokenResponse {
    pub access_token: String,
    pub refresh_token: String,
    pub expires_in: u64,
    pub refresh_expires_in: u64,
    pub open_id: String,
    pub scope: String,
    pub token_type: String,
}

// ============================================================================
// Privacy Level Constants
// ============================================================================

/// Privacy levels for TikTok posts
pub struct PrivacyLevel;

impl PrivacyLevel {
    /// Video visible to everyone
    pub const PUBLIC: &'static str = "PUBLIC_TO_EVERYONE";
    /// Video visible only to mutual followers
    pub const FRIENDS: &'static str = "MUTUAL_FOLLOW_FRIENDS";
    /// Video visible only to creator
    pub const PRIVATE: &'static str = "SELF_ONLY";
    /// Video visible to followers only
    pub const FOLLOWERS: &'static str = "FOLLOWER_OF_CREATOR";
}

// ============================================================================
// Scopes
// ============================================================================

/// OAuth scopes for TikTok API
pub struct TikTokScopes;

impl TikTokScopes {
    /// Basic user info (open_id, union_id, avatar)
    pub const USER_INFO_BASIC: &'static str = "user.info.basic";
    /// Extended user profile (bio, verified status, stats)
    pub const USER_INFO_PROFILE: &'static str = "user.info.profile";
    /// User's email address
    pub const USER_INFO_STATS: &'static str = "user.info.stats";
    /// List user's videos
    pub const VIDEO_LIST: &'static str = "video.list";
    /// Upload and publish videos
    pub const VIDEO_PUBLISH: &'static str = "video.publish";
    /// Upload videos to user's inbox
    pub const VIDEO_UPLOAD: &'static str = "video.upload";

    /// Get recommended scopes for content posting
    pub fn content_posting_scopes() -> &'static str {
        "user.info.basic,video.publish"
    }

    /// Get all available scopes
    pub fn all_scopes() -> &'static str {
        "user.info.basic,user.info.profile,user.info.stats,video.list,video.publish,video.upload"
    }
}

// ============================================================================
// Helper Functions
// ============================================================================

/// Build hashtag string from list
pub fn build_hashtags(tags: &[String]) -> String {
    tags.iter()
        .map(|t| {
            if t.starts_with('#') {
                t.clone()
            } else {
                format!("#{}", t)
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

/// Validate video file for TikTok upload
pub fn validate_video_file(size_bytes: u64, duration_seconds: u32) -> Result<(), ChannelError> {
    // TikTok limits: 4GB max, typically 60s for regular users, up to 10 min for some
    const MAX_SIZE: u64 = 4 * 1024 * 1024 * 1024; // 4GB
    const MAX_DURATION: u32 = 600; // 10 minutes

    if size_bytes > MAX_SIZE {
        return Err(ChannelError::ApiError {
            code: Some("VIDEO_TOO_LARGE".to_string()),
            message: format!(
                "Video file too large: {} bytes (max: {} bytes)",
                size_bytes, MAX_SIZE
            ),
        });
    }

    if duration_seconds > MAX_DURATION {
        return Err(ChannelError::ApiError {
            code: Some("VIDEO_TOO_LONG".to_string()),
            message: format!(
                "Video too long: {} seconds (max: {} seconds)",
                duration_seconds, MAX_DURATION
            ),
        });
    }

    Ok(())
}

/// Supported video formats for TikTok
pub struct VideoFormats;

impl VideoFormats {
    pub const MP4: &'static str = "video/mp4";
    pub const WEBM: &'static str = "video/webm";
    pub const MOV: &'static str = "video/quicktime";

    pub fn is_supported(content_type: &str) -> bool {
        matches!(
            content_type.to_lowercase().as_str(),
            "video/mp4" | "video/webm" | "video/quicktime"
        )
    }
}

/// Supported image formats for TikTok photo posts
pub struct ImageFormats;

impl ImageFormats {
    pub const JPEG: &'static str = "image/jpeg";
    pub const PNG: &'static str = "image/png";
    pub const WEBP: &'static str = "image/webp";

    pub fn is_supported(content_type: &str) -> bool {
        matches!(
            content_type.to_lowercase().as_str(),
            "image/jpeg" | "image/png" | "image/webp"
        )
    }
}
