use crate::DbPool;
use crate::api::ClientVersion;
use crate::providers::{Provider, report_provider_error, requester_or_default};
use crate::status::*;
use crate::util::cache::VideoCache;
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 regex::Regex;
use scraper::{ElementRef, Html, Selector};
use wreq::Version;

pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
    crate::providers::ProviderChannelMetadata {
        group_id: "studio-network",
        tags: &["premium", "studios", "networks", "models"],
    };

const BASE_URL: &str = "https://www.wow.xxx";
const CHANNEL_ID: &str = "wowxxx";
const FIREFOX_UA: &str =
    "Mozilla/5.0 (X11; Linux x86_64; rv:147.0) Gecko/20100101 Firefox/147.0";
const HTML_ACCEPT: &str =
    "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8";

error_chain! {
    foreign_links {
        Io(std::io::Error);
    }
    errors {
        Parse(msg: String) {
            description("parse error")
            display("parse error: {}", msg)
        }
    }
}

#[derive(Debug, Clone)]
enum Target {
    Latest,
    MostPopular,
    TopRated,
    Search(String),
    Archive(String),
}

#[derive(Debug, Clone)]
pub struct WowxxxProvider;

impl WowxxxProvider {
    pub fn new() -> Self {
        Self
    }

    fn build_channel(&self, _cv: ClientVersion) -> Channel {
        Channel {
            id: CHANNEL_ID.to_string(),
            name: "WOW.XXX".to_string(),
            description:
                "Premium studio aggregator with latest updates, search, and direct archive routing for sites/networks/models/tags/categories."
                    .to_string(),
            premium: false,
            favicon: "https://www.google.com/s2/favicons?sz=64&domain=wow.xxx".to_string(),
            status: "active".to_string(),
            categories: vec![],
            options: vec![ChannelOption {
                id: "sort".to_string(),
                title: "Sort".to_string(),
                description: "Browse WOW.XXX feeds.".to_string(),
                systemImage: "list.number".to_string(),
                colorName: "blue".to_string(),
                options: vec![
                    FilterOption {
                        id: "new".to_string(),
                        title: "Latest".to_string(),
                    },
                    FilterOption {
                        id: "popular".to_string(),
                        title: "Most Viewed".to_string(),
                    },
                    FilterOption {
                        id: "rated".to_string(),
                        title: "Top Rated".to_string(),
                    },
                ],
                multiSelect: false,
            }],
            nsfw: true,
            cacheDuration: Some(1800),
            ytdlpCommand: Some("yt-dlp --impersonate chrome-120".to_string())
        }
    }

    fn selector(value: &str) -> Result<Selector> {
        Selector::parse(value)
            .map_err(|e| Error::from(format!("selector `{value}` parse failed: {e}")))
    }

    fn decode_html(text: &str) -> String {
        decode(text.as_bytes())
            .to_string()
            .unwrap_or_else(|_| text.to_string())
    }

    fn text_of(el: &ElementRef<'_>) -> String {
        let raw = el.text().collect::<Vec<_>>().join(" ");
        Self::decode_html(&raw.split_whitespace().collect::<Vec<_>>().join(" "))
    }

    fn normalize_url(path: &str) -> String {
        let path = path.trim();
        if path.starts_with("http://") || path.starts_with("https://") {
            return path.to_string();
        }
        if path.starts_with("//") {
            return format!("https:{path}");
        }
        if path.starts_with('/') {
            return format!("{BASE_URL}{path}");
        }
        format!("{BASE_URL}/{path}")
    }

    fn slug_from_url(url: &str, segment: &str) -> Option<String> {
        let marker = format!("/{segment}/");
        let (_, tail) = url.split_once(&marker)?;
        let slug = tail.trim_matches('/').split('/').next()?.trim();
        if slug.is_empty() {
            None
        } else {
            Some(slug.to_string())
        }
    }

    fn normalize_search_query(query: &str) -> String {
        let mut cleaned = query.trim().trim_start_matches('#').to_string();
        while cleaned.contains("//") {
            cleaned = cleaned.replace("//", "/");
        }
        cleaned
    }

    fn page_url(base: &str, page: u16) -> String {
        let base = base.trim_end_matches('/');
        if page <= 1 {
            format!("{base}/")
        } else {
            format!("{base}/{page}/")
        }
    }

    fn target_url(target: &Target, page: u16) -> String {
        match target {
            Target::Latest => Self::page_url(&format!("{BASE_URL}/latest-updates"), page),
            Target::MostPopular => Self::page_url(&format!("{BASE_URL}/most-popular/all"), page),
            Target::TopRated => Self::page_url(&format!("{BASE_URL}/top-rated/all"), page),
            Target::Search(query) => {
                let encoded: String =
                    url::form_urlencoded::byte_serialize(query.trim().as_bytes()).collect();
                let base = format!("{BASE_URL}/search/{encoded}/relevance");
                Self::page_url(&base, page)
            }
            Target::Archive(base) => Self::page_url(base, page),
        }
    }

    fn resolve_query_target(query: &str) -> Option<Target> {
        let q = query.trim();
        let lower = q.to_ascii_lowercase();
        let prefixes = [
            ("site:", "sites"),
            ("studio:", "sites"),
            ("network:", "networks"),
            ("model:", "models"),
            ("pornstar:", "models"),
            ("tag:", "tags"),
            ("cat:", "categories"),
            ("category:", "categories"),
        ];
        for (prefix, segment) in prefixes {
            if let Some((_, value)) = lower.split_once(prefix) {
                let slug = value.trim().trim_matches('/');
                if !slug.is_empty() {
                    return Some(Target::Archive(format!("{BASE_URL}/{segment}/{slug}/")));
                }
            }
        }
        None
    }

    fn resolve_target(query: Option<&str>, sort: &str) -> Target {
        if let Some(q) = query {
            let trimmed = q.trim();
            if !trimmed.is_empty() {
                if let Some(shortcut) = Self::resolve_query_target(trimmed) {
                    return shortcut;
                }
                return Target::Search(Self::normalize_search_query(trimmed));
            }
        }

        match sort {
            "popular" => Target::MostPopular,
            "rated" => Target::TopRated,
            _ => Target::Latest,
        }
    }

    fn html_headers(referer: &str) -> Vec<(String, String)> {
        vec![
            ("User-Agent".to_string(), FIREFOX_UA.to_string()),
            ("Accept".to_string(), HTML_ACCEPT.to_string()),
            ("Accept-Language".to_string(), "en-US,en;q=0.5".to_string()),
            ("Referer".to_string(), referer.to_string()),
        ]
    }

    async fn fetch_html(
        requester: &mut crate::util::requester::Requester,
        url: &str,
    ) -> Result<String> {
        requester
            .get_with_headers(url, Self::html_headers(BASE_URL), Some(Version::HTTP_11))
            .await
            .map_err(|e| Error::from(format!("request failed for {url}: {e}")))
    }

    fn parse_duration(duration_text: &str) -> u32 {
        let cleaned = duration_text
            .replace("Full Video", "")
            .replace("Full", "")
            .trim()
            .to_string();
        parse_time_to_seconds(&cleaned).unwrap_or(0) as u32
    }

    fn parse_video_items(html: &str) -> Result<Vec<VideoItem>> {
        let document = Html::parse_document(html);

        let item_sel = Self::selector("div.list-videos div.item")?;
        let a_sel = Self::selector("a.thumb_img")?;
        let title_sel = Self::selector("a.thumb_title strong.title, a.thumb_title")?;
        let img_sel = Self::selector("div.thumb__img img")?;
        let thumb_wrap_sel = Self::selector("div.thumb__img")?;
        let duration_sel = Self::selector("span.duration")?;
        let rating_sel = Self::selector("div.rating")?;
        let views_sel = Self::selector("div.views")?;
        let site_sel = Self::selector("a.models__item.thumb_cs")?;
        let model_sel = Self::selector("a.models__item.thumb_model")?;
        let id_sel = Self::selector("span.js-favourites[data-object_id]")?;

        let rating_re = Regex::new(r"(\d{1,3})").map_err(|e| Error::from(e.to_string()))?;

        let mut out = vec![];
        for item in document.select(&item_sel) {
            let Some(anchor) = item.select(&a_sel).next() else {
                continue;
            };
            let href = anchor.value().attr("href").unwrap_or("").trim();
            if href.is_empty() {
                continue;
            }
            let video_url = Self::normalize_url(href);

            let id = item
                .select(&id_sel)
                .next()
                .and_then(|el| el.value().attr("data-object_id"))
                .map(|v| v.to_string())
                .or_else(|| {
                    let tail = video_url.trim_end_matches('/').split('/').next_back()?;
                    if tail.is_empty() {
                        None
                    } else {
                        Some(tail.to_string())
                    }
                })
                .unwrap_or_default();
            if id.is_empty() {
                continue;
            }

            let title = item
                .select(&title_sel)
                .next()
                .map(|el| Self::text_of(&el))
                .filter(|value| !value.is_empty())
                .unwrap_or_else(|| {
                    anchor
                        .value()
                        .attr("title")
                        .map(Self::decode_html)
                        .unwrap_or_else(|| "Untitled".to_string())
                });

            let thumb = item
                .select(&img_sel)
                .next()
                .and_then(|img| {
                    img.value()
                        .attr("data-src")
                        .or_else(|| img.value().attr("src"))
                })
                .map(Self::normalize_url)
                .unwrap_or_default();
            if thumb.is_empty() {
                continue;
            }

            let preview = item
                .select(&thumb_wrap_sel)
                .next()
                .and_then(|el| el.value().attr("data-preview"))
                .map(Self::normalize_url)
                .filter(|value| !value.is_empty());

            let duration = item
                .select(&duration_sel)
                .next()
                .map(|el| Self::parse_duration(&Self::text_of(&el)))
                .unwrap_or(0);

            let views = item
                .select(&views_sel)
                .next()
                .map(|el| Self::text_of(&el))
                .and_then(|v| parse_abbreviated_number(v.trim()))
                .unwrap_or(0);

            let rating = item
                .select(&rating_sel)
                .next()
                .map(|el| Self::text_of(&el))
                .and_then(|raw| {
                    rating_re
                        .captures(&raw)
                        .and_then(|caps| caps.get(1))
                        .and_then(|m| m.as_str().parse::<f32>().ok())
                });

            let uploader_name = item
                .select(&site_sel)
                .next()
                .map(|el| Self::text_of(&el))
                .filter(|value| !value.is_empty());
            let uploader_url = item
                .select(&site_sel)
                .next()
                .and_then(|el| el.value().attr("href"))
                .map(Self::normalize_url);
            let uploader_id = uploader_url
                .as_deref()
                .and_then(|url| Self::slug_from_url(url, "sites"))
                .map(|slug| format!("{CHANNEL_ID}:site:{slug}"));

            let mut tags = vec![];
            for model in item.select(&model_sel) {
                let value = Self::text_of(&model);
                if !value.is_empty() {
                    tags.push(value);
                }
            }

            let mut video = VideoItem::new(
                id,
                title,
                video_url,
                CHANNEL_ID.to_string(),
                thumb,
                duration,
            )
            .views(views)
            .tags(tags);

            if let Some(r) = rating {
                video = video.rating(r);
            }
            if let Some(preview) = preview {
                video = video.preview(preview);
            }
            if let Some(uploader) = uploader_name {
                video = video.uploader(uploader);
            }
            if let Some(url) = uploader_url {
                video = video.uploader_url(url);
            }
            if let Some(uid) = uploader_id {
                video.uploaderId = Some(uid);
            }

            out.push(video);
        }

        Ok(out)
    }

    async fn get(
        &self,
        _db: DbPool,
        _cache: VideoCache,
        page: u16,
        sort: &str,
        query: Option<String>,
        options: ServerOptions,
    ) -> Result<Vec<VideoItem>> {
        let target = Self::resolve_target(query.as_deref(), sort);
        let url = Self::target_url(&target, page);

        let mut requester = requester_or_default(&options, CHANNEL_ID, "get_videos");
        let html = Self::fetch_html(&mut requester, &url).await?;

        Self::parse_video_items(&html)
    }
}

#[async_trait]
impl Provider for WowxxxProvider {
    async fn get_videos(
        &self,
        cache: VideoCache,
        db: DbPool,
        sort: String,
        query: Option<String>,
        page: String,
        _per_page: String,
        options: ServerOptions,
    ) -> Vec<VideoItem> {
        let page = page.parse::<u16>().unwrap_or(1).max(1);
        match self.get(db, cache, page, &sort, query, options).await {
            Ok(v) => v,
            Err(e) => {
                report_provider_error(CHANNEL_ID, "get_videos", &e.to_string()).await;
                vec![]
            }
        }
    }

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