use std::vec;
use error_chain::error_chain;
use futures::future::join_all;
use wreq::Client;
use wreq_util::Emulation;
use crate::db;
use crate::providers::Provider;
use crate::util::cache::VideoCache;
use crate::videos::{self, ServerOptions, VideoItem};
use crate::DbPool;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(wreq::Error);
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct HanimeSearchRequest{
    search_text: String,
    tags: Vec<String>,
    tags_mode: String,
    brands: Vec<String>,
    blacklist: Vec<String>,
    order_by: String,
    ordering: String,
    page: u8
}

#[allow(dead_code)]
impl HanimeSearchRequest {
    pub fn new() -> Self {
        HanimeSearchRequest {
            search_text: "".to_string(),
            tags: vec![],
            tags_mode: "AND".to_string(),
            brands: vec![],
            blacklist: vec![],
            order_by: "created_at_unix".to_string(),
            ordering: "desc".to_string(),
            page: 0
        }
    }
    pub fn tags(mut self, tags: Vec<String>) -> Self {
        self.tags = tags;
        self
    }
    pub fn search_text(mut self, search_text: String) -> Self {
        self.search_text = search_text;
        self
    }
    pub fn tags_mode(mut self, tags_mode: String) -> Self {
        self.tags_mode = tags_mode;
        self
    }
    pub fn brands(mut self, brands: Vec<String>) -> Self {
        self.brands = brands;
        self
    }
    pub fn blacklist(mut self, blacklist: Vec<String>) -> Self {
        self.blacklist = blacklist;
        self
    }
    pub fn order_by(mut self, order_by: String) -> Self {
        self.order_by = order_by;
        self
    }
    pub fn ordering(mut self, ordering: String) -> Self {
        self.ordering = ordering;
        self
    }
    pub fn page(mut self, page: u8) -> Self {
        self.page = page;
        self
    }
    
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct HanimeSearchResponse{
    page: u8,
    nbPages:u8,
    nbHits: u32,
    hitsPerPage: u8,
    hits: String
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct HanimeSearchResult{
    id: u64,
    name: String,
    titles: Vec<String>,
    slug: String,
    description: String,
    views: u64,
    interests: u64,
    poster_url: String,
    cover_url: String,
    brand: String,
    brand_id: u64,
    duration_in_ms: u32,
    is_censored: bool,
    rating: Option<u32>,
    likes: u64,
    dislikes: u64,
    downloads: u64,
    monthly_ranked: Option<u64>,
    tags: Vec<String>,
    created_at: u64,
    released_at: u64,

}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct HanimeProvider {
    url: String,
}

impl HanimeProvider {
    pub fn new() -> Self {
        HanimeProvider {
            url: "https://hanime.tv/".to_string(),
        }
    }

    async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool) -> Result<VideoItem> {
        let mut conn = pool.get().expect("couldn't get db connection from pool");
        let db_result = db::get_video(&mut conn,format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone()));
        drop(conn);
        let id = hit.id.to_string();
        let title = hit.name;
        let thumb = hit.poster_url;
        let duration = (hit.duration_in_ms / 1000) as u32; // Convert ms to seconds
        let channel = "hanime".to_string(); // Placeholder, adjust as needed
        match db_result {
            Ok(Some(video_url)) => {
                return Ok(VideoItem::new(id, title, video_url.clone(), channel, thumb, duration)
                    .tags(hit.tags)
                    .uploader(hit.brand)
                    .views(hit.views as u32)
                    .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32)
                    .formats(vec![videos::VideoFormat::new(video_url.clone(), "1080".to_string(), "m3u8".to_string())]));
            }
            Ok(None) => (),
            Err(e) => {
                println!("Error fetching video from database: {}", e);
                // return Err(format!("Error fetching video from database: {}", e).into());
            }
        }

        let client = Client::builder()
        .emulation(Emulation::Firefox136)
        .build()?;
        let url = format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug);
        let response = client.get(url).send().await?;
        
        let text = match response.status().is_success() {
            true => {
                response.text().await?
            },
            false => {
                print!("Failed to fetch video item: {}\n\n", response.status());
                return Err(format!("Failed to fetch video item: {}", response.status()).into());
            }        
        };
        
        let urls = text.split("\"servers\"").collect::<Vec<&str>>()[1];
        let mut url_vec = vec![];

        for el in urls.split("\"url\":\"").collect::<Vec<&str>>(){
            let url = el.split("\"").collect::<Vec<&str>>()[0];
            if !url.is_empty() && url.contains("m3u8") {
                url_vec.push(url.to_string());
            }
        }
        let mut conn = pool.get().expect("couldn't get db connection from pool");
        let _ = db::insert_video(&mut conn, &format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone()), &url_vec[0].clone());
        drop(conn);
        Ok(VideoItem::new(id, title, url_vec[0].clone(), channel, thumb, duration)
            .tags(hit.tags)
            .uploader(hit.brand)
            .views(hit.views as u32)
            .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32)
        .formats(vec![videos::VideoFormat::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())]))
        
    }

    async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String, sort:String) -> Result<Vec<VideoItem>> {
        let index = format!("hanime:{}:{}:{}", query, page, sort);
        let order_by = match sort.contains("."){
            true => sort.split(".").collect::<Vec<&str>>()[0].to_string(),
            false => "created_at_unix".to_string(),
        };
        let ordering = match sort.contains("."){
            true => sort.split(".").collect::<Vec<&str>>()[1].to_string(),
            false => "desc".to_string(),
        };
        let old_items = match cache.get(&index) {
            Some((time, items)) => {
                if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 * 12 {
                    println!("Cache hit for URL: {}", index);
                    return Ok(items.clone());
                }
                else{
                    items.clone()
                }
            }
            None => {
                vec![]
            }
        };

        let search = HanimeSearchRequest::new()
            .page(page-1)
            .search_text(query.clone())
            .order_by(order_by)
            .ordering(ordering);
        let client = Client::builder()
        .emulation(Emulation::Firefox136)
        .build()?;
        let response = client.post("https://search.htv-services.com/search")
        .json(&search)
        .send().await?;

        

        let hits = match response.json::<HanimeSearchResponse>().await {
            Ok(resp) => resp.hits,
            Err(e) => {
                println!("Failed to parse HanimeSearchResponse: {}", e);
                return Ok(old_items);
            }
        };
        let hits_json: Vec<HanimeSearchResult> = serde_json::from_str(hits.as_str())
            .map_err(|e| format!("Failed to parse hits JSON: {}", e))?;
        // let timeout_duration = Duration::from_secs(120);
        let futures = hits_json.into_iter().map(|el| self.get_video_item(el.clone(), pool.clone()));
        let results: Vec<Result<VideoItem>> = join_all(futures).await;
        let video_items: Vec<VideoItem> = results
            .into_iter()
            .filter_map(Result::ok)
            .collect();     
        if !video_items.is_empty() {
                cache.remove(&index);
                cache.insert(index.clone(), video_items.clone());
            } else {
                return Ok(old_items);
            }

        Ok(video_items)
    }
}

impl Provider for HanimeProvider {
    async fn get_videos(
        &self,
        cache: VideoCache,
        pool: DbPool,
        sort: String,
        query: Option<String>,
        page: String,
        per_page: String,
        options: ServerOptions,
    ) -> Vec<VideoItem> {
        let _ = options;
        let _ = per_page;
        let _ = sort;
        let videos: std::result::Result<Vec<VideoItem>, Error> = match query {
            Some(q) => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), q, sort).await,
            None => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), "".to_string(), sort).await,
        };
        match videos {
            Ok(v) => v,
            Err(e) => {
                println!("Error fetching videos: {}", e);
                vec![]
            }
        }
    }
}
