use anyhow::{Context, Result};
use core::time::Duration;
use futures::{pin_mut, stream::StreamExt, FutureExt};
use std::io::Write;
use tempfile::NamedTempFile;
use tokio::time::sleep;
use tokio_retry::{
    strategy::{jitter, FibonacciBackoff},
    Retry,
};

use crate::utils::{match_media_msg, random_user_with_random_convo};

#[tokio::test]
async fn image_blurhash_support() -> Result<()> {
    let _ = env_logger::try_init();

    let (mut user, room_id) = random_user_with_random_convo("image_blurhash").await?;
    let state_sync = user.start_sync();
    state_sync.await_has_synced_history().await?;

    // wait for sync to catch up
    let retry_strategy = FibonacciBackoff::from_millis(100).map(jitter).take(10);
    Retry::spawn(retry_strategy, || async {
        user.convo(room_id.to_string()).await
    })
    .await?;

    let convo = user.convo(room_id.to_string()).await?;
    let timeline = convo.timeline_stream();
    let stream = timeline.messages_stream();
    pin_mut!(stream);

    let bytes = include_bytes!("../fixtures/kingfisher.jpg");
    let mut tmp_jpg = NamedTempFile::new()?;
    tmp_jpg.as_file_mut().write_all(bytes)?;
    let jpg_name = tmp_jpg // it is randomly generated by system and not kingfisher.jpg
        .path()
        .file_name()
        .expect("it is not file")
        .to_string_lossy()
        .to_string();

    let mimetype = "image/jpeg";
    let blurhash = "KingFisher";
    let draft = user
        .image_draft(
            tmp_jpg.path().to_string_lossy().to_string(),
            mimetype.to_owned(),
        )
        .blurhash(blurhash.to_owned())
        .clone(); // switch variable from temporary to normal so that send_message can use it
    timeline.send_message(Box::new(draft)).await?;

    // image msg may reach via pushback action or reset action
    let mut i = 30;
    let mut found = None;
    while i > 0 {
        if let Some(diff) = stream.next().now_or_never().flatten() {
            match diff.action().as_str() {
                "PushBack" => {
                    let value = diff
                        .value()
                        .expect("diff pushback action should have valid value");
                    if let Some(msg_content) = match_media_msg(&value, mimetype, &jpg_name) {
                        found = Some(msg_content);
                    }
                }
                "Set" => {
                    let value = diff
                        .value()
                        .expect("diff set action should have valid value");
                    if let Some(msg_content) = match_media_msg(&value, mimetype, &jpg_name) {
                        found = Some(msg_content);
                    }
                }
                "Reset" => {
                    let values = diff
                        .values()
                        .expect("diff reset action should have valid values");
                    for value in values.iter() {
                        if let Some(msg_content) = match_media_msg(value, mimetype, &jpg_name) {
                            found = Some(msg_content);
                            break;
                        }
                    }
                }
                _ => {}
            }
            // yay
            if found.is_some() {
                break;
            }
        }
        i -= 1;
        sleep(Duration::from_secs(1)).await;
    }
    let msg_content = found.context("Even after 30 seconds, image msg not received")?;
    assert_eq!(
        msg_content.blurhash().as_deref(),
        Some(blurhash),
        "image blurhash not available",
    );

    Ok(())
}

#[tokio::test]
async fn video_blurhash_support() -> Result<()> {
    let _ = env_logger::try_init();

    let (mut user, room_id) = random_user_with_random_convo("video_blurhash").await?;
    let state_sync = user.start_sync();
    state_sync.await_has_synced_history().await?;

    // wait for sync to catch up
    let retry_strategy = FibonacciBackoff::from_millis(100).map(jitter).take(10);
    Retry::spawn(retry_strategy, || async {
        user.convo(room_id.to_string()).await
    })
    .await?;

    let convo = user.convo(room_id.to_string()).await?;
    let timeline = convo.timeline_stream();
    let stream = timeline.messages_stream();
    pin_mut!(stream);

    let bytes = include_bytes!("../fixtures/big_buck_bunny.mp4");
    let mut tmp_mp4 = NamedTempFile::new()?;
    tmp_mp4.as_file_mut().write_all(bytes)?;
    let mp4_name = tmp_mp4 // it is randomly generated by system and not big_buck_bunny.mp4
        .path()
        .file_name()
        .expect("it is not file")
        .to_string_lossy()
        .to_string();

    let mimetype = "video/mp4";
    let blurhash = "Big Buck Bunny";
    let draft = user
        .video_draft(
            tmp_mp4.path().to_string_lossy().to_string(),
            mimetype.to_owned(),
        )
        .blurhash(blurhash.to_owned())
        .clone(); // switch variable from temporary to normal so that send_message can use it
    timeline.send_message(Box::new(draft)).await?;

    // video msg may reach via pushback action or reset action
    let mut i = 30;
    let mut found = None;
    while i > 0 {
        if let Some(diff) = stream.next().now_or_never().flatten() {
            match diff.action().as_str() {
                "PushBack" => {
                    let value = diff
                        .value()
                        .expect("diff pushback action should have valid value");
                    if let Some(msg_content) = match_media_msg(&value, mimetype, &mp4_name) {
                        found = Some(msg_content);
                    }
                }
                "Set" => {
                    let value = diff
                        .value()
                        .expect("diff set action should have valid value");
                    if let Some(msg_content) = match_media_msg(&value, mimetype, &mp4_name) {
                        found = Some(msg_content);
                    }
                }
                "Reset" => {
                    let values = diff
                        .values()
                        .expect("diff reset action should have valid values");
                    for value in values.iter() {
                        if let Some(msg_content) = match_media_msg(value, mimetype, &mp4_name) {
                            found = Some(msg_content);
                            break;
                        }
                    }
                }
                _ => {}
            }
            // yay
            if found.is_some() {
                break;
            }
        }
        i -= 1;
        sleep(Duration::from_secs(1)).await;
    }
    let msg_content = found.context("Even after 30 seconds, video msg not received")?;
    assert_eq!(
        msg_content.blurhash().as_deref(),
        Some(blurhash),
        "video blurhash not available",
    );

    Ok(())
}
