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::VideoFormat;
use crate::videos::ServerOptions;
use crate::videos::VideoItem;
use async_trait::async_trait;
use diesel::r2d2;
use error_chain::error_chain;
use futures::stream::{self, StreamExt};
use htmlentity::entity::{ICodedDataTrait, decode};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
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,
    tag_map: Arc<RwLock<HashMap<String, String>>>,
}

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

    fn normalize_key(value: &str) -> String {
        value
            .trim()
            .to_ascii_lowercase()
            .replace(['_', '-'], " ")
            .split_whitespace()
            .collect::<Vec<_>>()
            .join(" ")
    }

    fn humanize_slug(value: &str) -> String {
        value
            .trim_matches('/')
            .replace('-', " ")
            .split_whitespace()
            .collect::<Vec<_>>()
            .join(" ")
    }

    fn insert_tag_mapping(&self, key: &str, path_or_url: &str) {
        let normalized = Self::normalize_key(key);
        if normalized.is_empty() || path_or_url.trim().is_empty() {
            return;
        }
        if let Ok(mut map) = self.tag_map.write() {
            map.insert(normalized, path_or_url.trim().to_string());
        }
    }

    fn resolve_query_url(&self, query: &str, page: u8, sort: &str) -> Option<String> {
        let normalized = Self::normalize_key(query);
        let mapped = self.tag_map.read().ok()?.get(&normalized)?.clone();
        let separator = if mapped.contains('?') { "&" } else { "?" };
        let mut url = format!("{mapped}{separator}page={page}");
        if !sort.is_empty() {
            url.push_str("&sort=");
            url.push_str(sort);
        }
        Some(url)
    }

    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,
            ytdlpCommand: Some("yt-dlp --impersonate chrome-120".to_string())
        }
    }

    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 mut url_str = format!(
            "{}/{}/search/{}?page={}{}",
            self.url, language, search_string, page, sort
        );
        if let Some(mapped_url) = self.resolve_query_url(query, page, &sort.replace("&sort=", "")) {
            url_str = mapped_url;
        }

        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());
                }
            }
        }

        stream::iter(urls.into_iter().map(|url| {
            let pool = pool.clone();
            let requester = requester.clone();
            let provider = self.clone();
            async move { provider.get_video_item(url, pool, requester).await }
        }))
        .buffer_unordered(4)
        .filter_map(|result| async move { result.ok() })
        .collect::<Vec<_>>()
        .await
    }

    async fn get_video_item(
        &self,
        url_str: String,
        pool: DbPool,
        mut requester: Requester,
    ) -> Result<VideoItem> {
        // 1. Attempt live fetch
        let fetched = self.fetch_video_item(&url_str, &mut requester).await;

        match fetched {
            Ok(video_item) => {
                // Store/override in 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)
            }
            Err(e) => {
                // 2. Fall back to DB
                if let Ok(mut conn) = pool.get() {
                    if let Ok(Some(entry)) = db::get_video(&mut conn, url_str.clone()) {
                        if let Ok(mut video_item) = serde_json::from_str::<VideoItem>(entry.as_str()) {
                            video_item.url = url_str.clone();
                            return Ok(video_item);
                        }
                    }
                }
                Err(e)
            }
        }
    }

    async fn fetch_video_item(
        &self,
        url_str: &str,
        requester: &mut Requester,
    ) -> Result<VideoItem> {
        let vid = requester
            .get(url_str, Some(Version::HTTP_2))
            .await
            .map_err(|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!(),
                );
                Error::from(e.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();

        // Extract Tags
        let mut tags = vec![];
        for (label, route_kind) in [
            ("Actress:", "actress"),
            ("Actor:", "actor"),
            ("Maker:", "maker"),
            ("Genre:", "genre"),
        ] {
            let marker = format!("<span>{}</span>", label);
            if let Some(section) = extract(&vid, &marker, "</div>") {
                for anchor in section.split("<a ").skip(1) {
                    let href = anchor
                        .split("href=\"")
                        .nth(1)
                        .and_then(|value| value.split('"').next())
                        .unwrap_or_default()
                        .to_string();
                    let title = anchor
                        .split("class=\"text-nord13 font-medium\">")
                        .nth(1)
                        .and_then(|value| value.split('<').next())
                        .map(str::trim)
                        .unwrap_or_default()
                        .to_string();
                    if !title.is_empty() {
                        tags.push(title.clone());
                        if !href.is_empty() {
                            let full_url = if href.starts_with("http://") || href.starts_with("https://") {
                                href.clone()
                            } else {
                                format!("{}{}", self.url, href)
                            };
                            self.insert_tag_mapping(&title, &full_url);
                            let slug = href
                                .trim_matches('/')
                                .rsplit('/')
                                .next()
                                .unwrap_or_default()
                                .to_string();
                            if !slug.is_empty() {
                                self.insert_tag_mapping(&slug, &full_url);
                                self.insert_tag_mapping(
                                    &format!("{route_kind}:{}", slug),
                                    &full_url,
                                );
                                self.insert_tag_mapping(
                                    &format!("{route_kind}:{}", Self::humanize_slug(&slug)),
                                    &full_url,
                                );
                            }
                        }
                    }
                }
            }
        }

        // Extract Video URL (m3u8)
        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 mut format = VideoFormat::new(video_url.clone(), "auto".to_string(), "m3u8".to_string());
        format.add_http_header("Referer".to_string(), "https://missav.ws/".to_string());
        let video_item = VideoItem::new(id, title, url_str.to_string(), "missav".to_string(), thumb, duration)
            .formats(vec![format])
            .tags(tags)
            .preview(format!(
                "https://fourhoi.com/{}/preview.mp4",
                url_str.split('/').last().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))
    }
}
