use async_trait::async_trait;
use futures::FutureExt;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap as HashMap;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;

use crate::{
    DbPool,
    api::ClientVersion,
    status::Channel,
    util::{cache::VideoCache, discord::send_discord_error_report, requester::Requester},
    videos::{ServerOptions, VideoItem},
};

pub mod all;
pub mod hanime;
pub mod homoxxx;
pub mod okporn;
pub mod okxxx;
pub mod perfectgirls;
pub mod perverzija;
pub mod pmvhaven;
pub mod pornhat;
pub mod pornhub;
pub mod redtube;
pub mod rule34video;
pub mod spankbang;
// pub mod hentaimoon;
pub mod beeg;
pub mod missav;
pub mod omgxxx;
pub mod paradisehill;
pub mod porn00;
pub mod porn4fans;
pub mod pornzog;
pub mod sxyprn;
pub mod tnaflix;
pub mod tokyomotion;
pub mod viralxxxporn;
pub mod xfree;
pub mod xxthots;
pub mod youjizz;
// pub mod pornxp;
pub mod chaturbate;
pub mod freepornvideosxxx;
pub mod hentaihaven;
pub mod hqporner;
pub mod hypnotube;
pub mod javtiful;
pub mod noodlemagazine;
pub mod pimpbunny;
pub mod rule34gen;
pub mod xxdbx;
// pub mod tube8;

// convenient alias
pub type DynProvider = Arc<dyn Provider>;

pub static ALL_PROVIDERS: Lazy<HashMap<&'static str, DynProvider>> = Lazy::new(|| {
    let mut m = HashMap::default();
    m.insert("all", Arc::new(all::AllProvider::new()) as DynProvider);
    m.insert(
        "perverzija",
        Arc::new(perverzija::PerverzijaProvider::new()) as DynProvider,
    );
    m.insert(
        "hanime",
        Arc::new(hanime::HanimeProvider::new()) as DynProvider,
    );
    m.insert(
        "pornhub",
        Arc::new(pornhub::PornhubProvider::new()) as DynProvider,
    );
    m.insert(
        "spankbang",
        Arc::new(spankbang::SpankbangProvider::new()) as DynProvider,
    );
    m.insert(
        "rule34video",
        Arc::new(rule34video::Rule34videoProvider::new()) as DynProvider,
    );
    m.insert(
        "redtube",
        Arc::new(redtube::RedtubeProvider::new()) as DynProvider,
    );
    m.insert(
        "okporn",
        Arc::new(okporn::OkpornProvider::new()) as DynProvider,
    );
    m.insert(
        "pornhat",
        Arc::new(pornhat::PornhatProvider::new()) as DynProvider,
    );
    m.insert(
        "perfectgirls",
        Arc::new(perfectgirls::PerfectgirlsProvider::new()) as DynProvider,
    );
    m.insert(
        "okxxx",
        Arc::new(okxxx::OkxxxProvider::new()) as DynProvider,
    );
    m.insert(
        "homoxxx",
        Arc::new(homoxxx::HomoxxxProvider::new()) as DynProvider,
    );
    m.insert(
        "missav",
        Arc::new(missav::MissavProvider::new()) as DynProvider,
    );
    m.insert(
        "xxthots",
        Arc::new(xxthots::XxthotsProvider::new()) as DynProvider,
    );
    m.insert(
        "sxyprn",
        Arc::new(sxyprn::SxyprnProvider::new()) as DynProvider,
    );
    m.insert(
        "porn00",
        Arc::new(porn00::Porn00Provider::new()) as DynProvider,
    );
    m.insert(
        "youjizz",
        Arc::new(youjizz::YoujizzProvider::new()) as DynProvider,
    );
    m.insert(
        "paradisehill",
        Arc::new(paradisehill::ParadisehillProvider::new()) as DynProvider,
    );
    m.insert(
        "porn4fans",
        Arc::new(porn4fans::Porn4fansProvider::new()) as DynProvider,
    );
    m.insert(
        "pornzog",
        Arc::new(pornzog::PornzogProvider::new()) as DynProvider,
    );
    m.insert(
        "omgxxx",
        Arc::new(omgxxx::OmgxxxProvider::new()) as DynProvider,
    );
    m.insert("beeg", Arc::new(beeg::BeegProvider::new()) as DynProvider);
    m.insert(
        "tnaflix",
        Arc::new(tnaflix::TnaflixProvider::new()) as DynProvider,
    );
    m.insert(
        "tokyomotion",
        Arc::new(tokyomotion::TokyomotionProvider::new()) as DynProvider,
    );
    m.insert(
        "viralxxxporn",
        Arc::new(viralxxxporn::ViralxxxpornProvider::new()) as DynProvider,
    );
    // m.insert("pornxp", Arc::new(pornxp::PornxpProvider::new()) as DynProvider);
    m.insert(
        "rule34gen",
        Arc::new(rule34gen::Rule34genProvider::new()) as DynProvider,
    );
    m.insert(
        "xxdbx",
        Arc::new(xxdbx::XxdbxProvider::new()) as DynProvider,
    );
    m.insert(
        "xfree",
        Arc::new(xfree::XfreeProvider::new()) as DynProvider,
    );
    m.insert(
        "hqporner",
        Arc::new(hqporner::HqpornerProvider::new()) as DynProvider,
    );
    m.insert(
        "pmvhaven",
        Arc::new(pmvhaven::PmvhavenProvider::new()) as DynProvider,
    );
    m.insert(
        "noodlemagazine",
        Arc::new(noodlemagazine::NoodlemagazineProvider::new()) as DynProvider,
    );
    m.insert(
        "pimpbunny",
        Arc::new(pimpbunny::PimpbunnyProvider::new()) as DynProvider,
    );
    m.insert(
        "javtiful",
        Arc::new(javtiful::JavtifulProvider::new()) as DynProvider,
    );
    m.insert(
        "hypnotube",
        Arc::new(hypnotube::HypnotubeProvider::new()) as DynProvider,
    );
    m.insert(
        "freepornvideosxxx",
        Arc::new(freepornvideosxxx::FreepornvideosxxxProvider::new()) as DynProvider,
    );
    m.insert(
        "hentaihaven",
        Arc::new(hentaihaven::HentaihavenProvider::new()) as DynProvider,
    );
    m.insert(
        "chaturbate",
        Arc::new(chaturbate::ChaturbateProvider::new()) as DynProvider,
    );
    // m.insert("tube8", Arc::new(tube8::Tube8Provider::new()) as DynProvider);
    // add more here as you migrate them
    m
});

pub fn init_providers_now() {
    // Idempotent & thread-safe: runs the Lazy init exactly once.
    Lazy::force(&ALL_PROVIDERS);
}

pub fn panic_payload_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
    if let Some(s) = payload.downcast_ref::<&str>() {
        return (*s).to_string();
    }
    if let Some(s) = payload.downcast_ref::<String>() {
        return s.clone();
    }
    "unknown panic payload".to_string()
}

pub async fn run_provider_guarded<F>(provider_name: &str, context: &str, fut: F) -> Vec<VideoItem>
where
    F: Future<Output = Vec<VideoItem>>,
{
    match AssertUnwindSafe(fut).catch_unwind().await {
        Ok(videos) => videos,
        Err(payload) => {
            let panic_msg = panic_payload_to_string(payload);
            let _ = send_discord_error_report(
                format!("Provider panic: {}", provider_name),
                None,
                Some("Provider Guard"),
                Some(&format!("context={}; panic={}", context, panic_msg)),
                file!(),
                line!(),
                module_path!(),
            )
            .await;
            vec![]
        }
    }
}

pub async fn report_provider_error(provider_name: &str, context: &str, msg: &str) {
    let _ = send_discord_error_report(
        format!("Provider error: {}", provider_name),
        None,
        Some("Provider Guard"),
        Some(&format!("context={}; error={}", context, msg)),
        file!(),
        line!(),
        module_path!(),
    )
    .await;
}

pub fn report_provider_error_background(provider_name: &str, context: &str, msg: &str) {
    let provider_name = provider_name.to_string();
    let context = context.to_string();
    let msg = msg.to_string();
    tokio::spawn(async move {
        report_provider_error(&provider_name, &context, &msg).await;
    });
}

pub fn requester_or_default(
    options: &ServerOptions,
    provider_name: &str,
    context: &str,
) -> Requester {
    match options.requester.clone() {
        Some(requester) => requester,
        None => {
            report_provider_error_background(
                provider_name,
                context,
                "ServerOptions.requester missing; using default Requester",
            );
            Requester::new()
        }
    }
}

#[async_trait]
pub trait Provider: Send + Sync {
    async fn get_videos(
        &self,
        cache: VideoCache,
        pool: DbPool,
        sort: String,
        query: Option<String>,
        page: String,
        per_page: String,
        options: ServerOptions,
    ) -> Vec<VideoItem>;

    fn get_channel(&self, clientversion: ClientVersion) -> Option<Channel> {
        println!(
            "Getting channel for placeholder with client version: {:?}",
            clientversion
        );
        let _ = clientversion;
        Some(Channel {
            id: "placeholder".to_string(),
            name: "PLACEHOLDER".to_string(),
            description: "PLACEHOLDER FOR PARENT CLASS".to_string(),
            premium: false,
            favicon: "https://www.google.com/s2/favicons?sz=64&domain=missav.ws".to_string(),
            status: "active".to_string(),
            categories: vec![],
            options: vec![],
            nsfw: true,
            cacheDuration: None,
        })
    }
}
