use crate::DbPool;
use crate::api::ClientVersion;
use crate::providers::Provider;
use crate::status::*;
use crate::util::cache::VideoCache;
use crate::util::discord::{format_error_chain, send_discord_error_report};
use crate::util::parse_abbreviated_number;
use crate::util::time::parse_time_to_seconds;
use crate::videos::{ServerOptions, VideoItem};
use async_trait::async_trait;
use error_chain::error_chain;
use htmlentity::entity::{ICodedDataTrait, decode};
use std::vec;

pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
    crate::providers::ProviderChannelMetadata {
        group_id: "mainstream-tube",
        tags: &["mainstream", "legacy", "studio"],
    };

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(wreq::Error);
    }
}

#[derive(Debug, Clone)]
pub struct TnaflixProvider {
    url: String,
}

impl TnaflixProvider {
    pub fn new() -> Self {
        TnaflixProvider {
            url: "https://www.tnaflix.com".to_string(),
        }
    }

    fn build_channel(&self, _clientversion: ClientVersion) -> Channel {
        Channel {
            id: "tnaflix".to_string(),
            name: "TnAflix".to_string(),
            description: "Just Tits and Ass".to_string(),
            premium: false,
            favicon: "https://www.google.com/s2/favicons?sz=64&domain=www.tnaflix.com".to_string(),
            status: "active".to_string(),
            categories: vec![],
            options: vec![
                ChannelOption {
                    id: "sort".to_string(),
                    title: "Sort".to_string(),
                    description: "Sort the Videos".to_string(),
                    systemImage: "list.number".to_string(),
                    colorName: "blue".to_string(),
                    options: vec![
                        FilterOption {
                            id: "new".into(),
                            title: "New".into(),
                        },
                        FilterOption {
                            id: "featured".into(),
                            title: "Featured".into(),
                        },
                        FilterOption {
                            id: "toprated".into(),
                            title: "Top Rated".into(),
                        },
                    ],
                    multiSelect: false,
                },
                ChannelOption {
                    id: "duration".to_string(),
                    title: "Duration".to_string(),
                    description: "Length of the Videos".to_string(),
                    systemImage: "timer".to_string(),
                    colorName: "green".to_string(),
                    options: vec![
                        FilterOption {
                            id: "all".into(),
                            title: "All".into(),
                        },
                        FilterOption {
                            id: "short".into(),
                            title: "Short (1-3 min)".into(),
                        },
                        FilterOption {
                            id: "medium".into(),
                            title: "Medium (3-10 min)".into(),
                        },
                        FilterOption {
                            id: "long".into(),
                            title: "Long (10-30 min)".into(),
                        },
                        FilterOption {
                            id: "full".into(),
                            title: "Full length (30+ min)".into(),
                        },
                    ],
                    multiSelect: false,
                },
            ],
            nsfw: true,
            cacheDuration: None,
            ytdlpCommand: Some("yt-dlp --impersonate chrome-120".to_string())
        }
    }

    async fn get(
        &self,
        cache: VideoCache,
        page: u8,
        sort: &str,
        options: ServerOptions,
    ) -> Result<Vec<VideoItem>> {
        let sort_string = match sort {
            "featured" => "featured",
            "toprated" => "toprated",
            _ => "new",
        };
        let duration_string = options
            .duration
            .clone()
            .unwrap_or_else(|| "all".to_string());

        let video_url = format!(
            "{}/{}/{}?d={}",
            self.url, sort_string, page, duration_string
        );

        // Cache Logic
        if let Some((time, items)) = cache.get(&video_url) {
            if time.elapsed().unwrap_or_default().as_secs() < 300 {
                return Ok(items.clone());
            }
        }

        let mut requester = options.requester.clone().ok_or("Requester missing")?;
        let text = requester
            .get(&video_url, None)
            .await
            .map_err(|e| format!("{}", e))?;

        let video_items = self.get_video_items_from_html(text);
        if !video_items.is_empty() {
            cache.insert(video_url, video_items.clone());
        }
        Ok(video_items)
    }

    async fn query(
        &self,
        cache: VideoCache,
        page: u8,
        query: &str,
        options: ServerOptions,
    ) -> Result<Vec<VideoItem>> {
        let search_string = query.to_lowercase().trim().replace(" ", "+");
        let duration_string = options
            .duration
            .clone()
            .unwrap_or_else(|| "all".to_string());

        let video_url = format!(
            "{}/search?what={}&d={}&page={}",
            self.url, search_string, duration_string, page
        );

        if let Some((time, items)) = cache.get(&video_url) {
            if time.elapsed().unwrap_or_default().as_secs() < 300 {
                return Ok(items.clone());
            }
        }

        let mut requester = options.requester.clone().ok_or("Requester missing")?;
        let text = requester
            .get(&video_url, None)
            .await
            .map_err(|e| format!("{}", e))?;

        let video_items = self.get_video_items_from_html(text);
        if !video_items.is_empty() {
            cache.insert(video_url, video_items.clone());
        }
        Ok(video_items)
    }

    fn get_video_items_from_html(&self, html: String) -> Vec<VideoItem> {
        if html.is_empty() {
            return vec![];
        }

        let mut items = Vec::new();

        // Safe helper for splitting
        let get_part = |input: &str, sep: &str, idx: usize| -> Option<String> {
            input.split(sep).nth(idx).map(|s| s.to_string())
        };

        // Navigate to the video list container safely
        let list_part = match html.split("row video-list").nth(1) {
            Some(p) => match p.split("pagination ").next() {
                Some(inner) => inner,
                None => return vec![],
            },
            None => return vec![],
        };

        let raw_videos: Vec<&str> = list_part
            .split("col-xs-6 col-md-4 col-lg-4 col-xl-3 mb-3")
            .skip(1)
            .collect();

        for (idx, segment) in raw_videos.iter().enumerate() {
            let item: Option<VideoItem> = (|| {
                let video_url = get_part(segment, " href=\"", 1)?
                    .split("\"")
                    .next()?
                    .to_string();

                let mut title = get_part(segment, "class=\"video-title text-break\">", 1)?
                    .split("<")
                    .next()?
                    .trim()
                    .to_string();
                title = decode(title.as_bytes()).to_string().unwrap_or(title);

                let id = video_url.split("/").nth(5)?.to_string();

                let thumb = if segment.contains("data-src=\"") {
                    get_part(segment, "data-src=\"", 1)?
                        .split("\"")
                        .next()?
                        .to_string()
                } else {
                    get_part(segment, "<img src=\"", 1)?
                        .split("\"")
                        .next()?
                        .to_string()
                };

                let raw_duration = get_part(segment, "thumb-icon video-duration\">", 1)?
                    .split("<")
                    .next()?
                    .to_string();
                let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32;

                let views = if segment.contains("icon-eye\"></i>") {
                    let v_str = get_part(segment, "icon-eye\"></i>", 1)?
                        .split("<")
                        .next()?
                        .trim()
                        .to_string();
                    parse_abbreviated_number(&v_str).unwrap_or(0) as u32
                } else {
                    0
                };

                let preview = get_part(segment, "data-trailer=\"", 1)?
                    .split("\"")
                    .next()?
                    .to_string();

                Some(
                    VideoItem::new(id, title, video_url, "tnaflix".to_string(), thumb, duration)
                        .views(views)
                        .preview(preview),
                )
            })();

            if let Some(v) = item {
                items.push(v);
            } else {
                eprintln!("Tnaflix: Failed to parse item index {}", idx);
                tokio::spawn(async move {
                    let _ = send_discord_error_report(
                        format!("Tnaflix Parse Error at index {}", idx),
                        None,
                        Some("Tnaflix Provider"),
                        None,
                        file!(),
                        line!(),
                        module_path!(),
                    )
                    .await;
                });
            }
        }
        items
    }
}

#[async_trait]
impl Provider for TnaflixProvider {
    async fn get_videos(
        &self,
        cache: VideoCache,
        _pool: DbPool,
        sort: String,
        query: Option<String>,
        page: String,
        _per_page: String,
        options: ServerOptions,
    ) -> Vec<VideoItem> {
        let page_num = page.parse::<u8>().unwrap_or(1);

        let result = match query {
            Some(q) => self.query(cache, page_num, &q, options).await,
            None => self.get(cache, page_num, &sort, options).await,
        };

        match result {
            Ok(v) => v,
            Err(e) => {
                eprintln!("Tnaflix Error: {}", e);

                // 1. Create a collection of owned data so we don't hold references to `e`
                let mut error_reports = Vec::new();

                // Iterating through the error chain to collect data into owned Strings
                for cause in e.iter().skip(1) {
                    error_reports.push((
                        cause.to_string(),               // Title
                        format_error_chain(cause),       // Description/Chain
                        format!("caused by: {}", cause), // Message
                    ));
                }

                // 2. Now that we aren't holding any `&dyn StdError`, we can safely .await
                for (title, chain_str, msg) in error_reports {
                    let _ = send_discord_error_report(
                        title,
                        Some(chain_str),
                        Some("Pornzog Provider"),
                        Some(&msg),
                        file!(),
                        line!(),
                        module_path!(),
                    )
                    .await;
                }

                // In a real app, you'd extract owned strings here
                // and await your discord reporter as we did for Pornzog
                vec![]
            }
        }
    }

    fn get_channel(&self, clientversion: ClientVersion) -> Option<Channel> {
        Some(self.build_channel(clientversion))
    }
}
