use tokio::sync::mpsc;

use crate::db::Movie;
use crate::notify::{
    NotifyEvent, NotifySender, format_language_upgrade_text, format_milestone_text,
    format_monthly_stats_text, format_movie_text, format_removed_text, format_upgrade_text,
};
use crate::settings::{Telegram, TheMovieDb};
use crate::tmdb::poster_url;

/// Spawns the Telegram notification background task and returns a sender.
///
/// The task sets `TELOXIDE_TOKEN` from `config` once (safe: called before
/// any other tokio tasks touch the env) and then drains the channel,
/// sending one Telegram photo-message per event.
///
/// # Safety
/// `set_var` is called inside the spawned task before `Bot::from_env()`.
/// The caller must ensure no concurrent reads of `TELOXIDE_TOKEN` occur
/// between the spawn and the first `set_var` execution.
pub fn start(config: Telegram, tmdb: TheMovieDb, main_languages: Vec<String>) -> NotifySender {
    let (tx, mut rx) = mpsc::channel::<Vec<NotifyEvent>>(8);

    tokio::spawn(async move {
        // Safety: spawned before the accept loop; no other task reads this var yet.
        #[allow(unused_unsafe)]
        unsafe {
            std::env::set_var("TELOXIDE_TOKEN", &config.token);
        }
        while let Some(events) = rx.recv().await {
            for event in &events {
                match event {
                    NotifyEvent::New(m) => {
                        let cap = format_movie_text(m, &main_languages).await;
                        publish_movie(&config, &tmdb, m, cap).await;
                    }
                    NotifyEvent::Upgrade {
                        movie,
                        previous_resolution,
                    } => {
                        let cap =
                            format_upgrade_text(movie, previous_resolution, &main_languages).await;
                        publish_movie(&config, &tmdb, movie, cap).await;
                    }
                    NotifyEvent::Removed(m) => {
                        let cap = format_removed_text(m);
                        publish_text(&config, cap).await;
                    }
                    NotifyEvent::LanguageUpgrade {
                        movie,
                        new_languages,
                    } => {
                        let cap = format_language_upgrade_text(movie, new_languages).await;
                        publish_movie(&config, &tmdb, movie, cap).await;
                    }
                    NotifyEvent::Milestone { count } => {
                        publish_text(&config, format_milestone_text(*count)).await;
                    }
                    NotifyEvent::MonthlyStats {
                        month_name,
                        added_count,
                        per_user,
                        total_count,
                        total_size_bytes,
                    } => {
                        let cap = format_monthly_stats_text(
                            month_name,
                            *added_count,
                            per_user,
                            *total_count,
                            *total_size_bytes,
                        );
                        publish_text(&config, cap).await;
                    }
                }
            }
        }
    });

    tx
}

async fn publish_movie(
    telegram_settings: &Telegram,
    tmdb_settings: &TheMovieDb,
    movie: &Movie,
    caption: String,
) {
    use teloxide::prelude::*;
    use teloxide::types::InputFile;

    let bot = Bot::from_env();

    let Some(tmdb_id) = movie.tmdb_id else {
        return;
    };

    let poster_url = match poster_url(tmdb_settings, tmdb_id as u64).await {
        Ok(url) => url,
        Err(e) => {
            log::error!(
                "Failed to fetch `TMDb` poster for '{}' ({}): {e}",
                movie.title,
                movie.year
            );
            return;
        }
    };

    log::info!(
        "Sending Telegram notification for '{}' ({}).",
        movie.title,
        movie.year
    );
    // For channels we have to prepend -100 to the actual id.
    // https://stackoverflow.com/questions/71275683/how-to-have-telegram-bot-post-messages-to-my-channel-and-not-its-own-channel
    if let Err(e) = bot
        .send_photo(telegram_settings.chatid.clone(), InputFile::url(poster_url))
        .caption(caption)
        .await
    {
        log::error!(
            "Failed to send Telegram notification for '{}' ({}): {e}",
            movie.title,
            movie.year
        );
    }
}

async fn publish_text(telegram_settings: &Telegram, text: String) {
    use teloxide::prelude::*;

    let bot = Bot::from_env();
    if let Err(e) = bot
        .send_message(telegram_settings.chatid.clone(), text)
        .await
    {
        log::error!("Failed to send Telegram text notification: {e}");
    }
}

#[cfg(test)]
mod tests {
    use teloxide::prelude::*;

    use crate::{db::Movie, notify::format_movie_text};

    #[tokio::test]
    async fn publish_telegram_message() {
        dotenvy::dotenv().ok();
        assert!(
            std::env::var("TELOXIDE_TOKEN").is_ok(),
            "TELOXIDE_TOKEN must be set in the environment or backend/.env"
        );
        let bot = Bot::from_env();

        let _ = bot
            .send_message("-1001906511632".to_owned(), "Test publish_telegram_message")
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn movie_text_with_main_languages() {
        let movie = Movie {
            title: "Test Movie".into(),
            year: 1942,
            size: 7000000000,
            hash: "d41d8cd98f00b204e9800998ecf8427e".into(),
            tmdb_id: Some(4242),
            audio_languages: Some("de,en, ara  ,fr".into()),
            subtitle_languages: Some("en,de,fr,chi".into()),
            resolution: Some("1920x1080".into()),
            dynamic_range: Some("HDR".into()),
            bitrate: Some(5000000),
            user: "pablita".into(),
        };
        let main_languages = vec!["de".to_owned(), "en".to_owned()];
        let text = format_movie_text(&movie, &main_languages).await;
        assert_eq!(
            text,
            "pablita hat nun\nTest Movie (1942)\n\n1920x1080 HDR\n7.00 GB - 5.00 Mb/s\n\nTonspur: Deutsch, Englisch und weitere\nUntertitel: Deutsch, Englisch und weitere\n"
        );
    }

    #[tokio::test]
    async fn movie_text_no_main_languages() {
        let movie = Movie {
            title: "Test Movie".into(),
            year: 1942,
            size: 7000000000,
            hash: "d41d8cd98f00b204e9800998ecf8427e".into(),
            tmdb_id: Some(4242),
            audio_languages: Some("de,en".into()),
            subtitle_languages: Some("en".into()),
            resolution: Some("1920x1080".into()),
            dynamic_range: Some("HDR".into()),
            bitrate: Some(5000000),
            user: "pablita".into(),
        };
        let text = format_movie_text(&movie, &[]).await;
        assert_eq!(
            text,
            "pablita hat nun\nTest Movie (1942)\n\n1920x1080 HDR\n7.00 GB - 5.00 Mb/s\n\nTonspur: Deutsch und Englisch\nUntertitel: Englisch\n"
        );
    }
}
