use chrono::{SecondsFormat, TimeZone, Utc};
use serde::{Deserialize, Serialize};

use crate::videos::VideoItem;

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UploadersRequest {
    #[serde(default, alias = "uploader_id")]
    pub uploaderId: Option<String>,
    #[serde(default, alias = "uploader_name")]
    pub uploaderName: Option<String>,
    #[serde(default, alias = "profile_content")]
    pub profileContent: bool,
    #[serde(default)]
    pub query: Option<String>,
}

impl UploadersRequest {
    pub fn normalized(self) -> Self {
        Self {
            uploaderId: normalize_optional_string(self.uploaderId),
            uploaderName: normalize_optional_string(self.uploaderName),
            profileContent: self.profileContent,
            query: normalize_optional_string(self.query),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UploaderProfile {
    pub id: String,
    pub name: String,
    pub url: Option<String>,
    pub channel: Option<String>,
    pub verified: bool,
    pub videoCount: u64,
    pub totalViews: u64,
    #[serde(default)]
    pub channels: Option<Vec<UploaderChannelStat>>,
    #[serde(default, alias = "profile_picture_url")]
    pub avatar: Option<String>,
    pub description: Option<String>,
    pub bio: Option<String>,
    #[serde(default)]
    pub videos: Option<Vec<UploaderVideoRef>>,
    #[serde(default)]
    pub tapes: Option<Vec<serde_json::Value>>,
    #[serde(default)]
    pub playlists: Option<Vec<serde_json::Value>>,
    #[serde(default)]
    pub layout: Option<Vec<UploaderLayoutRow>>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UploaderChannelStat {
    pub channel: String,
    pub videoCount: u64,
    #[serde(default, alias = "first_seen_at")]
    pub firstSeenAt: Option<String>,
    #[serde(default, alias = "last_seen_at")]
    pub lastSeenAt: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UploaderVideoRef {
    pub id: String,
    pub url: String,
    pub title: String,
    pub duration: u32,
    pub channel: String,
    #[serde(default, alias = "uploaded_at")]
    pub uploadedAt: Option<String>,
    pub uploader: String,
    #[serde(alias = "uploader_id")]
    pub uploaderId: String,
    pub thumb: String,
    pub preview: Option<String>,
    pub views: u32,
    pub rating: u32,
    #[serde(default, alias = "aspect_ratio")]
    pub aspectRatio: Option<f32>,
}

impl UploaderVideoRef {
    #[allow(dead_code)]
    pub fn from_video_item(item: &VideoItem, uploader_name: &str, uploader_id: &str) -> Self {
        Self {
            id: item.id.clone(),
            url: item.url.clone(),
            title: item.title.clone(),
            duration: item.duration,
            channel: item.channel.clone(),
            uploadedAt: iso_timestamp_from_unix(item.uploadedAt),
            uploader: item
                .uploader
                .clone()
                .unwrap_or_else(|| uploader_name.to_string()),
            uploaderId: item
                .uploaderId
                .clone()
                .unwrap_or_else(|| uploader_id.to_string()),
            thumb: item.thumb.clone(),
            preview: item.preview.clone(),
            views: item.views.unwrap_or_default(),
            rating: item.rating.map(normalize_rating).unwrap_or_default(),
            aspectRatio: item.aspectRatio,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UploaderLayoutRow {
    #[serde(rename = "type")]
    pub rowType: UploaderLayoutRowType,
    pub title: Option<String>,
    #[serde(default, alias = "video_ids")]
    pub videoIds: Option<Vec<String>>,
}

impl UploaderLayoutRow {
    #[allow(dead_code)]
    pub fn horizontal(title: Option<String>, video_ids: Vec<String>) -> Self {
        Self {
            rowType: UploaderLayoutRowType::Horizontal,
            title,
            videoIds: Some(video_ids),
        }
    }

    #[allow(dead_code)]
    pub fn videos(title: Option<String>) -> Self {
        Self {
            rowType: UploaderLayoutRowType::Videos,
            title,
            videoIds: None,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub enum UploaderLayoutRowType {
    #[default]
    #[serde(rename = "videos")]
    Videos,
    #[serde(rename = "horizontal", alias = "horizontal_videos")]
    Horizontal,
}

pub fn normalize_optional_string(value: Option<String>) -> Option<String> {
    value.and_then(|value| {
        let trimmed = value.trim();
        (!trimmed.is_empty()).then(|| trimmed.to_string())
    })
}

#[allow(dead_code)]
pub fn iso_timestamp_from_unix(value: Option<u64>) -> Option<String> {
    let timestamp = value?;
    let dt = Utc.timestamp_opt(timestamp as i64, 0).single()?;
    Some(dt.to_rfc3339_opts(SecondsFormat::Millis, true))
}

#[allow(dead_code)]
fn normalize_rating(value: f32) -> u32 {
    value.clamp(0.0, 100.0).round() as u32
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn request_accepts_snake_case_aliases() {
        let request: UploadersRequest = serde_json::from_str(
            r#"{
                "uploader_id": "hsex:xihongshiddd",
                "uploader_name": "xihongshiddd",
                "profile_content": true,
                "query": "teacher"
            }"#,
        )
        .expect("request should decode");

        assert_eq!(request.uploaderId.as_deref(), Some("hsex:xihongshiddd"));
        assert_eq!(request.uploaderName.as_deref(), Some("xihongshiddd"));
        assert!(request.profileContent);
        assert_eq!(request.query.as_deref(), Some("teacher"));
    }

    #[test]
    fn layout_aliases_decode() {
        let row: UploaderLayoutRow = serde_json::from_str(
            r#"{
                "type": "horizontal_videos",
                "title": "For You",
                "video_ids": ["one", "two"]
            }"#,
        )
        .expect("row should decode");

        assert_eq!(row.rowType, UploaderLayoutRowType::Horizontal);
        assert_eq!(row.videoIds.as_ref().map(Vec::len), Some(2));
    }

    #[test]
    fn avatar_alias_decodes() {
        let profile: UploaderProfile = serde_json::from_str(
            r#"{
                "id": "abc",
                "name": "Example",
                "verified": false,
                "videoCount": 1,
                "totalViews": 2,
                "profile_picture_url": "https://example.com/a.jpg"
            }"#,
        )
        .expect("profile should decode");

        assert_eq!(profile.avatar.as_deref(), Some("https://example.com/a.jpg"));
    }
}
