use ntex::web;
use regex::Regex;
use std::collections::HashMap;
use url::Url;

use crate::util::requester::Requester;

#[derive(Debug, Clone)]
pub struct HqpornerProxy {}

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

    fn normalize_detail_request(endpoint: &str) -> Option<(String, Option<u16>)> {
        let endpoint = endpoint.trim().trim_start_matches('/');
        if endpoint.is_empty() {
            return None;
        }

        let (detail_part, quality) = match endpoint.split_once("/__quality__/") {
            Some((detail, quality)) => {
                let requested = quality
                    .trim()
                    .trim_end_matches('/')
                    .trim_end_matches('p')
                    .parse::<u16>()
                    .ok();
                (detail, requested)
            }
            None => (endpoint, None),
        };

        let detail_url = if detail_part.starts_with("http://") || detail_part.starts_with("https://")
        {
            detail_part.to_string()
        } else {
            format!("https://{}", detail_part.trim_start_matches('/'))
        };

        Self::is_allowed_detail_url(&detail_url).then_some((detail_url, quality))
    }

    fn is_allowed_detail_url(url: &str) -> bool {
        let Some(url) = Url::parse(url).ok() else {
            return false;
        };
        if url.scheme() != "https" {
            return false;
        }
        let Some(host) = url.host_str() else {
            return false;
        };
        (host == "hqporner.com" || host == "www.hqporner.com") && url.path().starts_with("/hdporn/")
    }

    fn normalize_url(raw: &str) -> String {
        let value = raw.trim();
        if value.is_empty() {
            return String::new();
        }
        if value.starts_with("//") {
            return format!("https:{value}");
        }
        if value.starts_with('/') {
            return format!("https://www.hqporner.com{value}");
        }
        if value.starts_with("http://") {
            return value.replacen("http://", "https://", 1);
        }
        value.to_string()
    }

    fn regex(value: &str) -> Option<Regex> {
        Regex::new(value).ok()
    }

    fn extract_player_url(detail_html: &str) -> Option<String> {
        let pattern = r#"(?is)url\s*:\s*['"](/blocks/(?:altplayer|nativeplayer)\.php\?i=[^'"]+)['"]"#;
        let captures = Self::regex(pattern)?.captures(detail_html)?;
        let path = captures.get(1)?.as_str();
        Some(Self::normalize_url(path))
    }

    fn extract_source_url(player_html: &str) -> Option<String> {
        for source in player_html.split("<source ").skip(1) {
            let src = source
                .split("src=\\\"")
                .nth(1)
                .and_then(|s| s.split("\\\"").next())
                .or_else(|| {
                    source
                        .split("src=\"")
                        .nth(1)
                        .and_then(|s| s.split('"').next())
                })
                .unwrap_or_default();
            let url = Self::normalize_url(src);
            if !url.is_empty() {
                return Some(url);
            }
        }

        let iframe_regexes = [
            r#"(?is)<iframe[^>]+src="([^"]+)""#,
            r#"(?is)<iframe[^>]+src='([^']+)'"#,
            r#"(?is)src=\\\"([^\\"]+)\\\""#,
            r#"(?is)src=\\'([^\\']+)\\'"#,
        ];
        for pattern in iframe_regexes {
            let Some(regex) = Self::regex(pattern) else {
                continue;
            };
            if let Some(url) = regex
                .captures(player_html)
                .and_then(|caps| caps.get(1))
                .map(|m| Self::normalize_url(m.as_str()))
                .filter(|value| !value.is_empty())
            {
                return Some(url);
            }
        }

        let source_regex = Self::regex(r#"src=\\\"([^\\"]+)\\\""#)?;
        source_regex
            .captures(player_html)
            .and_then(|caps| caps.get(1))
            .map(|m| Self::normalize_url(m.as_str()))
            .filter(|value| !value.is_empty())
    }

    fn extract_quality_urls(video_page_html: &str) -> HashMap<u16, String> {
        let mut urls = HashMap::new();
        let Some(regex) =
            Self::regex(r#"(?i)(?:https?:)?//[^"'\\\s]+/pubs/[A-Za-z0-9._-]+/(360|720|1080)\.mp4"#)
        else {
            return urls;
        };

        for captures in regex.captures_iter(video_page_html) {
            let Some(full_match) = captures.get(0) else {
                continue;
            };
            let Some(quality_match) = captures.get(1) else {
                continue;
            };
            let Some(quality) = quality_match.as_str().parse::<u16>().ok() else {
                continue;
            };
            let normalized = Self::normalize_url(full_match.as_str());
            if !normalized.is_empty() {
                urls.insert(quality, normalized);
            }
        }

        urls
    }

    fn select_quality_url(quality_urls: &HashMap<u16, String>, requested: Option<u16>) -> Option<String> {
        let fallbacks = match requested.unwrap_or(1080) {
            1080 => [1080u16, 720, 360].as_slice(),
            720 => [720u16, 360].as_slice(),
            360 => [360u16].as_slice(),
            other if other > 1080 => [1080u16, 720, 360].as_slice(),
            other if other > 720 => [720u16, 360].as_slice(),
            _ => [360u16].as_slice(),
        };

        for quality in fallbacks {
            if let Some(url) = quality_urls.get(quality) {
                return Some(url.clone());
            }
        }

        if let Some(url) = quality_urls.get(&1080) {
            return Some(url.clone());
        }
        if let Some(url) = quality_urls.get(&720) {
            return Some(url.clone());
        }
        quality_urls.get(&360).cloned()
    }
}

impl crate::proxies::Proxy for HqpornerProxy {
    async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
        let Some((detail_url, requested_quality)) = Self::normalize_detail_request(&url) else {
            return String::new();
        };

        let mut requester = requester.get_ref().clone();
        let headers = vec![("Referer".to_string(), "https://hqporner.com/".to_string())];

        let detail_html = requester
            .get_with_headers(&detail_url, headers.clone(), None)
            .await
            .unwrap_or_default();
        if detail_html.is_empty() {
            return String::new();
        }

        let mut source_page_url = String::new();
        if let Some(player_url) = Self::extract_player_url(&detail_html) {
            let player_html = requester
                .get_with_headers(&player_url, headers.clone(), None)
                .await
                .unwrap_or_default();
            if !player_html.is_empty() {
                if let Some(url) = Self::extract_source_url(&player_html) {
                    source_page_url = url;
                }
            }
        }
        if source_page_url.is_empty() {
            source_page_url = Self::extract_source_url(&detail_html).unwrap_or_default();
        }
        if source_page_url.is_empty() {
            return String::new();
        }

        let source_page_html = requester
            .get_with_headers(&source_page_url, headers, None)
            .await
            .unwrap_or_default();
        if source_page_html.is_empty() {
            return String::new();
        }

        let quality_urls = Self::extract_quality_urls(&source_page_html);
        if quality_urls.is_empty() {
            return String::new();
        }

        Self::select_quality_url(&quality_urls, requested_quality).unwrap_or_default()
    }
}

#[cfg(test)]
mod tests {
    use super::HqpornerProxy;
    use std::collections::HashMap;

    #[test]
    fn extract_source_url_supports_iframe_src() {
        let html = r#"<iframe width="560" height="350" src="//mydaddy.cc/video/f7cbb41e218d3b1dca/&alt" frameborder="0" allowfullscreen=""></iframe>"#;
        let extracted = HqpornerProxy::extract_source_url(html);
        assert_eq!(
            extracted.as_deref(),
            Some("https://mydaddy.cc/video/f7cbb41e218d3b1dca/&alt")
        );
    }

    #[test]
    fn extract_source_url_supports_source_tag_src() {
        let html =
            r#"<video><source src=\"https://cdn.example.com/video.mp4\" type=\"video/mp4\"></video>"#;
        let extracted = HqpornerProxy::extract_source_url(html);
        assert_eq!(
            extracted.as_deref(),
            Some("https://cdn.example.com/video.mp4")
        );
    }

    #[test]
    fn extract_player_url_supports_altplayer_path() {
        let html = r#"
            <script>
              function altPlayer() {
                $.ajax({
                  type: 'POST',
                  url: '/blocks/altplayer.php?i=//mydaddy.cc/video/f7cbb41e218d3b1dca/',
                  success: function(data) {}
                });
              }
            </script>
        "#;
        let extracted = HqpornerProxy::extract_player_url(html);
        assert_eq!(
            extracted.as_deref(),
            Some(
                "https://www.hqporner.com/blocks/altplayer.php?i=//mydaddy.cc/video/f7cbb41e218d3b1dca/"
            )
        );
    }

    #[test]
    fn extract_quality_urls_from_mydaddy_html() {
        let html = r#"
            timelinePreview:{file:"//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/tile.vtt",spriteRelativePath:true,type:"VTT"}
            <source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4" title="360p" type="video/mp4" />
            <source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4" title="720p HD" type="video/mp4" />
            <source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/1080.mp4" title="1080p Full HD" type="video/mp4" />
        "#;

        let urls = HqpornerProxy::extract_quality_urls(html);
        assert_eq!(
            urls.get(&360).map(String::as_str),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4")
        );
        assert_eq!(
            urls.get(&720).map(String::as_str),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
        );
        assert_eq!(
            urls.get(&1080).map(String::as_str),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/1080.mp4")
        );
    }

    #[test]
    fn select_quality_url_falls_back_to_next_lower_quality() {
        let mut urls = HashMap::new();
        urls.insert(
            360,
            "https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4".to_string(),
        );
        urls.insert(
            720,
            "https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4".to_string(),
        );

        let requested_1080 = HqpornerProxy::select_quality_url(&urls, Some(1080));
        assert_eq!(
            requested_1080.as_deref(),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
        );

        let requested_720 = HqpornerProxy::select_quality_url(&urls, Some(720));
        assert_eq!(
            requested_720.as_deref(),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
        );

        let requested_360 = HqpornerProxy::select_quality_url(&urls, Some(360));
        assert_eq!(
            requested_360.as_deref(),
            Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4")
        );
    }
}
