use crate::DbPool;
use crate::api::ClientVersion;
use crate::db;
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::requester::Requester;
use crate::videos::ServerOptions;
use crate::videos::VideoItem;
use async_trait::async_trait;
use diesel::r2d2;
use error_chain::error_chain;
use futures::future::join_all;
use htmlentity::entity::{ICodedDataTrait, decode};
use std::vec;
use wreq::Version;

pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
    crate::providers::ProviderChannelMetadata {
        group_id: "jav",
        tags: &["jav", "asian", "uncensored"],
    };

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(wreq::Error);
        JsonError(serde_json::Error);
        Pool(r2d2::Error); // Assuming r2d2 or similar for pool
    }
    errors {
        ParsingError(t: String) {
            description("parsing error")
            display("Parsing error: '{}'", t)
        }
    }
}

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

impl MissavProvider {
    pub fn new() -> Self {
        MissavProvider {
            url: "https://missav.ws".to_string(),
        }
    }

    fn build_channel(&self, _clientversion: ClientVersion) -> Channel {
        Channel {
            id: "missav".to_string(),
            name: "MissAV".to_string(),
            description: "Watch HD JAV Online".to_string(),
            premium: false,
            favicon: "https://www.google.com/s2/favicons?sz=64&domain=missav.ws".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: "released_at".to_string(),
                            title: "Release Date".to_string(),
                        },
                        FilterOption {
                            id: "published_at".to_string(),
                            title: "Recent Update".to_string(),
                        },
                        FilterOption {
                            id: "today_views".to_string(),
                            title: "Today Views".to_string(),
                        },
                        FilterOption {
                            id: "weekly_views".to_string(),
                            title: "Weekly Views".to_string(),
                        },
                        FilterOption {
                            id: "monthly_views".to_string(),
                            title: "Monthly Views".to_string(),
                        },
                        FilterOption {
                            id: "views".to_string(),
                            title: "Total Views".to_string(),
                        },
                    ],
                    multiSelect: false,
                },
                ChannelOption {
                    id: "filter".to_string(),
                    title: "Filter".to_string(),
                    description: "Filter the Videos".to_string(),
                    systemImage: "line.horizontal.3.decrease.circle".to_string(),
                    colorName: "green".to_string(),
                    options: vec![
                        FilterOption {
                            id: "new".to_string(),
                            title: "Recent update".to_string(),
                        },
                        FilterOption {
                            id: "release".to_string(),
                            title: "New Releases".to_string(),
                        },
                        FilterOption {
                            id: "uncensored-leak".to_string(),
                            title: "Uncensored".to_string(),
                        },
                        FilterOption {
                            id: "english-subtitle".to_string(),
                            title: "English subtitle".to_string(),
                        },
                    ],
                    multiSelect: false,
                },
                ChannelOption {
                    id: "language".to_string(),
                    title: "Language".to_string(),
                    description: "What Language to fetch".to_string(),
                    systemImage: "flag.fill".to_string(),
                    colorName: "gray".to_string(),
                    options: vec![
                        FilterOption {
                            id: "en".to_string(),
                            title: "English".to_string(),
                        },
                        FilterOption {
                            id: "cn".to_string(),
                            title: "简体中文".to_string(),
                        },
                        FilterOption {
                            id: "ja".to_string(),
                            title: "日本語".to_string(),
                        },
                        FilterOption {
                            id: "ko".to_string(),
                            title: "한국의".to_string(),
                        },
                        FilterOption {
                            id: "ms".to_string(),
                            title: "Melayu".to_string(),
                        },
                        FilterOption {
                            id: "th".to_string(),
                            title: "ไทย".to_string(),
                        },
                        FilterOption {
                            id: "de".to_string(),
                            title: "Deutsch".to_string(),
                        },
                        FilterOption {
                            id: "fr".to_string(),
                            title: "Français".to_string(),
                        },
                        FilterOption {
                            id: "vi".to_string(),
                            title: "Tiếng Việt".to_string(),
                        },
                        FilterOption {
                            id: "id".to_string(),
                            title: "Bahasa Indonesia".to_string(),
                        },
                        FilterOption {
                            id: "fil".to_string(),
                            title: "Filipino".to_string(),
                        },
                        FilterOption {
                            id: "pt".to_string(),
                            title: "Português".to_string(),
                        },
                    ],
                    multiSelect: false,
                },
            ],
            nsfw: true,
            cacheDuration: None,
        }
    }

    async fn get(
        &self,
        cache: VideoCache,
        pool: DbPool,
        page: u8,
        mut sort: String,
        options: ServerOptions,
    ) -> Result<Vec<VideoItem>> {
        // Use ok_or to avoid unwrapping options
        let language = options.language.as_ref().ok_or("Missing language")?;
        let filter = options.filter.as_ref().ok_or("Missing filter")?;
        let mut requester = options.requester.clone().ok_or("Missing requester")?;

        if !sort.is_empty() {
            sort = format!("&sort={}", sort);
        }
        let url_str = format!("{}/{}/{}?page={}{}", self.url, language, filter, page, sort);

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

        let text = requester
            .get(&url_str, Some(Version::HTTP_2))
            .await
            .unwrap_or_else(|e| {
                eprintln!("Error fetching Missav URL {}: {}", url_str, e);
                let _ = send_discord_error_report(
                    e.to_string(),
                    None,
                    Some(&url_str),
                    None,
                    file!(),
                    line!(),
                    module_path!(),
                );
                "".to_string()
            });
        let video_items = self.get_video_items_from_html(text, pool, requester).await;

        if !video_items.is_empty() {
            cache.insert(url_str, video_items.clone());
        }
        Ok(video_items)
    }

    async fn query(
        &self,
        cache: VideoCache,
        pool: DbPool,
        page: u8,
        query: &str,
        mut sort: String,
        options: ServerOptions,
    ) -> Result<Vec<VideoItem>> {
        let language = options.language.as_ref().ok_or("Missing language")?;
        let mut requester = options.requester.clone().ok_or("Missing requester")?;

        let search_string = query.replace(" ", "%20");
        if !sort.is_empty() {
            sort = format!("&sort={}", sort);
        }
        let url_str = format!(
            "{}/{}/search/{}?page={}{}",
            self.url, language, search_string, page, sort
        );

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

        let text = requester
            .get(&url_str, Some(Version::HTTP_2))
            .await
            .unwrap_or_else(|e| {
                eprintln!("Error fetching Missav URL {}: {}", url_str, e);
                let _ = send_discord_error_report(
                    e.to_string(),
                    None,
                    Some(&url_str),
                    None,
                    file!(),
                    line!(),
                    module_path!(),
                );
                "".to_string()
            });
        let video_items = self.get_video_items_from_html(text, pool, requester).await;

        if !video_items.is_empty() {
            cache.insert(url_str, video_items.clone());
        }
        Ok(video_items)
    }

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

        let segments: Vec<&str> = html.split("@mouseenter=\"setPreview(\'").collect();
        if segments.len() < 2 {
            return vec![];
        }

        let mut urls = vec![];
        for video_segment in &segments[1..] {
            // Safer parsing: find start and end of href
            if let Some(start) = video_segment.find("<a href=\"") {
                let rest = &video_segment[start + 9..];
                if let Some(end) = rest.find('\"') {
                    urls.push(rest[..end].to_string());
                }
            }
        }

        let futures = urls
            .into_iter()
            .map(|url| self.get_video_item(url, pool.clone(), requester.clone()));
        join_all(futures)
            .await
            .into_iter()
            .filter_map(Result::ok)
            .collect()
    }

    async fn get_video_item(
        &self,
        url_str: String,
        pool: DbPool,
        mut requester: Requester,
    ) -> Result<VideoItem> {
        // 1. Database Check
        {
            let mut conn = pool
                .get()
                .map_err(|e| Error::from(format!("Pool error: {}", e)))?;
            if let Ok(Some(entry)) = db::get_video(&mut conn, url_str.clone()) {
                if let Ok(video_item) = serde_json::from_str::<VideoItem>(entry.as_str()) {
                    return Ok(video_item);
                }
            }
        }

        // 2. Fetch Page
        let vid = requester
            .get(&url_str, Some(Version::HTTP_2))
            .await
            .unwrap_or_else(|e| {
                eprintln!("Error fetching Missav URL {}: {}", url_str, e);
                let _ = send_discord_error_report(
                    e.to_string(),
                    None,
                    Some(&url_str),
                    None,
                    file!(),
                    line!(),
                    module_path!(),
                );
                "".to_string()
            });

        // Helper closure to extract content between two strings
        let extract = |html: &str, start_tag: &str, end_tag: &str| -> Option<String> {
            let start = html.find(start_tag)? + start_tag.len();
            let rest = &html[start..];
            let end = rest.find(end_tag)?;
            Some(rest[..end].to_string())
        };

        let mut title = extract(&vid, "<meta property=\"og:title\" content=\"", "\"")
            .ok_or_else(|| ErrorKind::ParsingError(format!("title\n{:?}", vid)))?;

        title = decode(title.as_bytes()).to_string().unwrap_or(title);
        if url_str.contains("uncensored") {
            title = format!("[Uncensored] {}", title);
        }

        let thumb =
            extract(&vid, "<meta property=\"og:image\" content=\"", "\"").unwrap_or_default();

        let duration = extract(
            &vid,
            "<meta property=\"og:video:duration\" content=\"",
            "\"",
        )
        .and_then(|d| d.parse::<u32>().ok())
        .unwrap_or(0);

        let id = url_str.split('/').last().ok_or("No ID found")?.to_string();

        // 3. Extract Tags (Generic approach to avoid repetitive code)
        let mut tags = vec![];
        for (label, prefix) in [
            ("Actress:", "@actress"),
            ("Actor:", "@actor"),
            ("Maker:", "@maker"),
            ("Genre:", "@genre"),
        ] {
            let marker = format!("<span>{}</span>", label);
            if let Some(section) = extract(&vid, &marker, "</div>") {
                for part in section.split("class=\"text-nord13 font-medium\">").skip(1) {
                    if let Some(val) = part.split('<').next() {
                        let clean = val.trim();
                        if !clean.is_empty() {
                            tags.push(format!("{}:{}", prefix, clean));
                        }
                    }
                }
            }
        }

        // 4. Extract Video URL (The m3u8 logic)
        let video_url = (|| {
            let parts_str = vid.split("m3u8").nth(1)?.split("https").next()?;
            let mut parts: Vec<&str> = parts_str.split('|').collect();
            parts.reverse();
            Some(format!(
                "https://{}.{}/{}-{}-{}-{}-{}/playlist.m3u8",
                parts.get(1)?,
                parts.get(2)?,
                parts.get(3)?,
                parts.get(4)?,
                parts.get(5)?,
                parts.get(6)?,
                parts.get(7)?
            ))
        })()
        .ok_or_else(|| ErrorKind::ParsingError(format!("video_url\n{:?}", vid).to_string()))?;

        let video_item =
            VideoItem::new(id, title, video_url, "missav".to_string(), thumb, duration)
                .tags(tags)
                .preview(format!(
                    "https://fourhoi.com/{}/preview.mp4",
                    url_str.split('/').last().unwrap_or_default()
                ));

        // 5. Cache to DB
        if let Ok(mut conn) = pool.get() {
            let _ = db::insert_video(
                &mut conn,
                &url_str,
                &serde_json::to_string(&video_item).unwrap_or_default(),
            );
        }

        Ok(video_item)
    }
}

#[async_trait]
impl Provider for MissavProvider {
    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, pool, page_num, &q, sort, options).await,
            None => self.get(cache, pool, page_num, sort, options).await,
        };

        result.unwrap_or_else(|e| {
            eprintln!("Error fetching videos: {}", e);
            let _ = send_discord_error_report(
                e.to_string(),
                Some(format_error_chain(&e)),
                None,
                None,
                file!(),
                line!(),
                module_path!(),
            );
            vec![]
        })
    }

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