use std::sync::Arc;

use tokio::sync::mpsc;
use whatsapp_rust::Jid;
use whatsapp_rust::bot::Bot;
use whatsapp_rust::download::MediaType;
use whatsapp_rust::types::events::Event;
use whatsapp_rust::upload::UploadResponse;
use whatsapp_rust_sqlite_storage::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;

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::{TheMovieDb, WhatsApp};
use crate::tmdb::poster_url;

/// Spawns the `WhatsApp` notification background task and returns a sender.
///
/// On first run the bot will log a QR code (scan with your phone). After
/// pairing the session is persisted in `config.db_path` and subsequent
/// runs reconnect automatically.
pub async fn start(
    config: WhatsApp,
    tmdb: TheMovieDb,
    main_languages: Vec<String>,
) -> color_eyre::eyre::Result<NotifySender> {
    let (tx, mut rx) = mpsc::channel::<Vec<NotifyEvent>>(8);

    let backend = Arc::new(SqliteStore::new(&config.db_path).await?);
    let jid_str = config.jid.clone();

    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .on_event(|event, _client| async move {
            match event {
                Event::PairingQrCode { code, .. } => {
                    log::info!("WhatsApp QR code (scan with your phone):");
                    if let Err(e) = qr2term::print_qr(&code) {
                        log::warn!("Failed to render QR code: {e}\nRaw: {code}");
                    }
                }
                Event::Connected(_) => {
                    log::info!("WhatsApp connected.");
                }
                Event::LoggedOut(_) => {
                    log::warn!("WhatsApp logged out.");
                }
                _ => {}
            }
        })
        .build()
        .await
        .map_err(|e| color_eyre::eyre::eyre!("WhatsApp bot build failed: {e}"))?;

    // Capture the client before running the bot so we can send messages later.
    let client = bot.client();

    // Forward channel messages to WhatsApp.
    tokio::spawn(async move {
        while let Some(events) = rx.recv().await {
            let jid: Jid = match jid_str.parse() {
                Ok(j) => j,
                Err(e) => {
                    log::error!("Invalid WhatsApp JID '{}': {e}", jid_str);
                    continue;
                }
            };
            for event in &events {
                match event {
                    NotifyEvent::New(m) => {
                        let cap = format_movie_text(m, &main_languages).await;
                        send_movie(&client, &tmdb, jid.clone(), m, cap).await;
                    }
                    NotifyEvent::Upgrade {
                        movie,
                        previous_resolution,
                    } => {
                        let cap =
                            format_upgrade_text(movie, previous_resolution, &main_languages).await;
                        send_movie(&client, &tmdb, jid.clone(), movie, cap).await;
                    }
                    NotifyEvent::Removed(m) => {
                        let cap = format_removed_text(m);
                        send_text(&client, jid.clone(), cap).await;
                    }
                    NotifyEvent::LanguageUpgrade {
                        movie,
                        new_languages,
                    } => {
                        let cap = format_language_upgrade_text(movie, new_languages).await;
                        send_movie(&client, &tmdb, jid.clone(), movie, cap).await;
                    }
                    NotifyEvent::Milestone { count } => {
                        send_text(&client, jid.clone(), 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,
                        );
                        send_text(&client, jid.clone(), cap).await;
                    }
                }
            }
        }
    });

    tokio::spawn(async move {
        match bot.run().await {
            Ok(handle) => {
                if let Err(e) = handle.await {
                    log::error!("WhatsApp bot task panicked: {e}");
                }
            }
            Err(e) => {
                log::error!("WhatsApp bot failed to start: {e}");
            }
        }
    });

    Ok(tx)
}

async fn send_text(client: &whatsapp_rust::client::Client, jid: Jid, text: String) {
    let message = whatsapp_rust::waproto::whatsapp::Message {
        conversation: Some(text),
        ..Default::default()
    };
    if let Err(e) = client.send_message(jid, message).await {
        log::error!("WhatsApp text send failed: {e}");
    }
}

async fn send_movie(
    client: &whatsapp_rust::client::Client,
    tmdb: &TheMovieDb,
    jid: Jid,
    movie: &Movie,
    caption: String,
) {
    // Try to fetch and upload the poster; fall back to text-only on error.
    let upload: Option<UploadResponse> = match movie.tmdb_id {
        Some(tmdb_id) => match poster_url(tmdb, tmdb_id as u64).await {
            Ok(url) => match reqwest::get(url).await {
                Ok(resp) => match resp.bytes().await {
                    Ok(bytes) => match client.upload(bytes.to_vec(), MediaType::Image).await {
                        Ok(u) => Some(u),
                        Err(e) => {
                            log::warn!(
                                "WhatsApp image upload failed for '{}' ({}): {e}",
                                movie.title,
                                movie.year
                            );
                            None
                        }
                    },
                    Err(e) => {
                        log::warn!("Failed to read poster bytes for '{}': {e}", movie.title);
                        None
                    }
                },
                Err(e) => {
                    log::warn!("Failed to download poster for '{}': {e}", movie.title);
                    None
                }
            },
            Err(e) => {
                log::warn!("Failed to fetch TMDb poster for '{}': {e}", movie.title);
                None
            }
        },
        None => None,
    };

    let message = match upload {
        Some(u) => whatsapp_rust::waproto::whatsapp::Message {
            image_message: Some(Box::new(
                whatsapp_rust::waproto::whatsapp::message::ImageMessage {
                    url: Some(u.url),
                    direct_path: Some(u.direct_path),
                    media_key: Some(u.media_key),
                    file_sha256: Some(u.file_sha256),
                    file_enc_sha256: Some(u.file_enc_sha256),
                    file_length: Some(u.file_length),
                    mimetype: Some("image/jpeg".to_string()),
                    caption: Some(caption),
                    ..Default::default()
                },
            )),
            ..Default::default()
        },
        None => whatsapp_rust::waproto::whatsapp::Message {
            conversation: Some(caption),
            ..Default::default()
        },
    };

    log::info!(
        "Sending WhatsApp notification for '{}' ({}).",
        movie.title,
        movie.year
    );
    if let Err(e) = client.send_message(jid, message).await {
        log::error!(
            "WhatsApp send failed for '{}' ({}): {e}",
            movie.title,
            movie.year
        );
    }
}
