use base64::{Engine as _, engine::general_purpose::STANDARD};
use ntex::web;
use serde::Deserialize;
use url::Url;

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

const BASE_URL: &str = "https://vjav.com";

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

#[derive(Debug, Deserialize, Clone, Default)]
struct VideofileEntry {
    #[serde(default)]
    video_url: String,
    #[serde(default)]
    is_default: i32,
}

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

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

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

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

    fn is_allowed_detail_url(url: &str) -> bool {
        let Some(parsed) = Url::parse(url).ok() else {
            return false;
        };

        if parsed.scheme() != "https" {
            return false;
        }

        let Some(host) = parsed.host_str() else {
            return false;
        };

        if host != "vjav.com" && host != "www.vjav.com" {
            return false;
        }

        let Some(video_id) = Self::extract_video_id(parsed.path()) else {
            return false;
        };

        !video_id.is_empty()
    }

    fn extract_video_id(path: &str) -> Option<String> {
        let mut segments = path.split('/').filter(|segment| !segment.is_empty());
        let first = segments.next()?;
        let second = segments.next()?;

        if first != "videos" {
            return None;
        }

        second
            .chars()
            .all(|value| value.is_ascii_digit())
            .then_some(second.to_string())
    }

    fn decode_obfuscated_base64(value: &str) -> String {
        value
            .chars()
            .map(|character| match character {
                'А' => 'A',
                'В' => 'B',
                'Е' => 'E',
                'К' => 'K',
                'М' => 'M',
                'Н' => 'H',
                'О' => 'O',
                'Р' => 'P',
                'С' => 'C',
                'Т' => 'T',
                'Х' => 'X',
                'а' => 'a',
                'е' => 'e',
                'о' => 'o',
                'р' => 'p',
                'с' => 'c',
                'у' => 'y',
                'х' => 'x',
                'к' => 'k',
                'м' => 'm',
                'і' => 'i',
                'І' => 'I',
                _ => character,
            })
            .collect()
    }

    fn decode_base64ish(value: &str) -> Option<String> {
        let mut normalized = value.trim().replace('~', "=");
        while normalized.len() % 4 != 0 {
            normalized.push('=');
        }
        let bytes = STANDARD.decode(normalized).ok()?;
        String::from_utf8(bytes).ok()
    }

    fn decode_video_url(value: &str) -> Option<String> {
        let normalized = Self::decode_obfuscated_base64(value);
        if normalized.contains(',') {
            let mut parts = normalized.split(',');
            let path_part = parts.next()?;
            let query_part = parts.next()?;

            let path = Self::decode_base64ish(path_part)?;
            let query = Self::decode_base64ish(query_part)?;
            let separator = if path.contains('?') { "&" } else { "?" };
            return Some(format!("{BASE_URL}{path}{separator}{query}&f=video.m3u8"));
        }

        let decoded = Self::decode_base64ish(&normalized)?;
        if decoded.starts_with("http://") || decoded.starts_with("https://") {
            return Some(decoded);
        }
        if decoded.starts_with('/') {
            return Some(format!("{BASE_URL}{decoded}"));
        }
        None
    }
}

impl crate::proxies::Proxy for VjavProxy {
    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(video_id) = Url::parse(&detail_url)
            .ok()
            .and_then(|value| Self::extract_video_id(value.path()))
        else {
            return String::new();
        };

        let api_url = format!("{BASE_URL}/api/videofile.php?video_id={video_id}&lifetime=8640000");

        let mut requester = requester.get_ref().clone();
        let text = requester.get(&api_url, None).await.unwrap_or_default();
        if text.is_empty() {
            return String::new();
        }

        let Ok(entries) = serde_json::from_str::<Vec<VideofileEntry>>(&text) else {
            return String::new();
        };

        let mut fallback = String::new();
        for entry in entries {
            if entry.video_url.trim().is_empty() {
                continue;
            }
            let Some(decoded) = Self::decode_video_url(&entry.video_url) else {
                continue;
            };
            if entry.is_default == 1 {
                return decoded;
            }
            if fallback.is_empty() {
                fallback = decoded;
            }
        }

        fallback
    }
}
