use std::sync::Arc;

use ntex::web::{self, HttpRequest};
use wreq::cookie::Jar;
use wreq::redirect::Policy;
use wreq_util::Emulation;

use crate::providers::strip_url_scheme;
use crate::util::hoster_proxy::proxy_name_for_url;
use crate::util::requester::Requester;

const BASE_URL: &str = "https://allpornstream.com";
const BROWSER_UA: &str =
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";

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

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

    fn build_chrome_client() -> Option<wreq::Client> {
        let jar = Arc::new(Jar::default());
        wreq::Client::builder()
            .cert_verification(false)
            .emulation(Emulation::Chrome120)
            .cookie_provider(jar)
            .redirect(Policy::default())
            .build()
            .ok()
    }

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

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

        let detail_url = detail_url.replacen("http://", "https://", 1);
        let parsed = url::Url::parse(&detail_url).ok()?;
        let host = parsed.host_str()?;
        if !(host == "allpornstream.com" || host == "www.allpornstream.com") {
            return None;
        }
        if !parsed.path().starts_with("/post/") {
            return None;
        }

        Some(detail_url)
    }

    fn request_headers() -> Vec<(String, String)> {
        vec![
            (
                "accept".to_string(),
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8".to_string(),
            ),
            ("accept-language".to_string(), "en-US,en;q=0.5".to_string()),
            ("user-agent".to_string(), BROWSER_UA.to_string()),
            ("referer".to_string(), BASE_URL.to_string()),
        ]
    }

    // Rank streaming hosts: lower = preferred
    fn host_rank(url: &str) -> u8 {
        if url.contains("voe.sx") {
            0
        } else if url.contains("dood") {
            1
        } else if url.contains("streamtape") {
            2
        } else if url.contains("filemoon") || url.contains("moonplayer") {
            3
        } else {
            4
        }
    }

    fn extract_stream_url(html: &str) -> Option<String> {
        // RSC data uses \" for quotes. Format inside script tags:
        // \"video_urls\":{\"link\":[[\"PROVIDER\",\"https://...\"],...]
        let needle = r#"\"video_urls\":{\"link\":["#;
        let pos = html.find(needle)?;
        let after = &html[pos + needle.len()..];

        // If the link array is empty ([]) there are no known hosting entries.
        // Do not scan further — the iframe section that follows contains embed
        // URLs for unknown providers (e.g. mydaddy.cc) that we cannot use.
        if after.starts_with(']') {
            return None;
        }

        // Capture up to the closing ]] of the link array (max 2000 chars)
        let array_end = after.find("]]").unwrap_or(after.len().min(2000));
        let array_str = &after[..array_end];

        // Extract all https:// URLs from within the array slice
        let mut candidates: Vec<String> = Vec::new();
        let mut remaining = array_str;
        while let Some(start) = remaining.find("https://") {
            let url_str = &remaining[start..];
            let end = url_str
                .find("\\\"")
                .or_else(|| url_str.find('"'))
                .unwrap_or(url_str.len().min(300));
            let url = &url_str[..end];
            if !url.is_empty() {
                candidates.push(url.to_string());
            }
            if end + 1 >= url_str.len() {
                break;
            }
            remaining = &remaining[start + end + 1..];
        }

        candidates.into_iter().min_by_key(|u| Self::host_rank(u))
    }
}

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

        let Some(client) = Self::build_chrome_client() else {
            return String::new();
        };
        let mut request = client.get(&detail_url);
        for (key, value) in Self::request_headers() {
            request = request.header(key, value);
        }
        let Ok(response) = request.send().await else {
            return String::new();
        };
        if !response.status().is_success() {
            return String::new();
        }
        let html = response.text().await.unwrap_or_default();
        if html.is_empty() {
            return String::new();
        }

        Self::extract_stream_url(&html).unwrap_or_default()
    }
}

/// Route handler for `/proxy/allpornstream/{endpoint}*`.
///
/// Fetches the allpornstream detail page, extracts the embedded hoster URL, rewrites
/// it to the corresponding local proxy URL (e.g. `/proxy/doodstream/…`), and returns
/// a 302 redirect. This lets the client resolve the final stream URL through the correct
/// per-hoster proxy rather than hitting the raw hoster URL directly.
pub async fn serve(
    req: HttpRequest,
    requester: web::types::State<Requester>,
) -> Result<impl web::Responder, web::Error> {
    let endpoint = req.match_info().query("endpoint").to_string();
    let Some(detail_url) = AllPornStreamProxy::normalize_detail_url(&endpoint) else {
        return Ok(web::HttpResponse::BadRequest().finish());
    };

    let Some(client) = AllPornStreamProxy::build_chrome_client() else {
        return Ok(web::HttpResponse::InternalServerError().finish());
    };
    let mut request = client.get(&detail_url);
    for (key, value) in AllPornStreamProxy::request_headers() {
        request = request.header(key, value);
    }
    let response = match request.send().await {
        Ok(r) if r.status().is_success() => r,
        _ => return Ok(web::HttpResponse::BadGateway().finish()),
    };
    let html = match response.text().await {
        Ok(h) if !h.is_empty() => h,
        _ => return Ok(web::HttpResponse::BadGateway().finish()),
    };

    let Some(hoster_url) = AllPornStreamProxy::extract_stream_url(&html) else {
        return Ok(web::HttpResponse::BadGateway().finish());
    };

    // For doodstream URLs call the proxy's Chrome extraction directly so
    // the /d/ → /e/ normalisation and Cloudflare bypass happen server-side.
    if proxy_name_for_url(&hoster_url) == Some("doodstream") {
        let embed_url = crate::proxies::doodstream::DoodstreamProxy::normalize_embed_url(&hoster_url)
            .unwrap_or(hoster_url.clone());
        let Some(cdn_url) = crate::proxies::doodstream::DoodstreamProxy::try_chrome_extraction(&embed_url).await else {
            return Ok(web::HttpResponse::BadGateway().finish());
        };
        return Ok(web::HttpResponse::Found()
            .header("Location", cdn_url)
            .finish());
    }

    // For other known hosters redirect to the corresponding local proxy.
    let redirect_url = match proxy_name_for_url(&hoster_url) {
        Some(proxy_name) => {
            let ci = req.connection_info();
            format!(
                "{}://{}/proxy/{}/{}",
                ci.scheme(),
                ci.host(),
                proxy_name,
                strip_url_scheme(&hoster_url)
            )
        }
        None => hoster_url,
    };

    Ok(web::HttpResponse::Found()
        .header("Location", redirect_url)
        .finish())
}
