use iced::{Animation, Function, advanced::image::Handle, widget::image};
use reqwest::Method;
use std::sync::Arc;

use crate::blur_hash::BlurHash;

#[derive(Clone)]
pub struct ImageDownloader {
    client: reqwest::Client,
    request_modifier:
        Option<Arc<dyn Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder + Send + Sync>>,
}

impl core::fmt::Debug for ImageDownloader {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("ImageDownloader")
            .field("client", &self.client)
            .field("request_modifier", &self.request_modifier.is_some())
            .finish()
    }
}

impl ImageDownloader {
    pub fn new() -> Self {
        Self {
            client: reqwest::Client::new(),
            request_modifier: None,
        }
    }

    pub fn with_modifier<F>(mut self, f: F) -> Self
    where
        F: Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder + Send + Sync + 'static,
    {
        self.request_modifier = Some(Arc::new(f));
        self
    }

    pub async fn download(&self, url: &str) -> reqwest::Result<bytes::Bytes> {
        use ::tap::*;
        let response = self
            .client
            .request(Method::GET, url)
            .pipe(|builder| {
                if let Some(ref modifier) = self.request_modifier {
                    modifier(builder)
                } else {
                    builder
                }
            })
            .send()
            .await?;
        let bytes = response.bytes().await?;
        Ok(bytes)
    }
}

#[derive(Clone, Debug)]
pub enum Preview {
    Thumbnail {
        thumbnail: Image,
        blur_hash: BlurHash,
    },
    BlurHash {
        blur_hash: BlurHash,
    },
}

#[derive(Debug, Clone)]
pub struct ImageCache {
    cache: std::collections::HashMap<uuid::Uuid, Preview>,
    downloader: ImageDownloader,
}

impl Preview {
    pub fn thumbnail(image: Image, blur_hash: BlurHash) -> Self {
        Preview::Thumbnail {
            thumbnail: image,
            blur_hash,
        }
    }

    pub fn blur_hash(blur_hash: BlurHash) -> Self {
        Preview::BlurHash { blur_hash }
    }

    // pub fn upgrade(
    //     self,
    //     fut: impl core::future::Future<Output = bytes::Bytes> + 'static + Send,
    // ) -> iced::Task<PreviewMessage> {
    //     // let sip = iced::task::sipper(async move |mut sender| {
    //     //     let bytes = fut.await;
    //     //     let handle = Handle::from_bytes(bytes.clone());
    //     //     let allocation = image::allocate(handle);
    //     //     let image = Image {
    //     //         bytes,
    //     //         handle,
    //     //         allocation,
    //     //         fade_in: Animation::new(false),
    //     //     };
    //     //     let _ = sender.send(image).await;
    //     // });
    //     // iced::Task::sip(sip, ||)
    //     Task::
    // }
}

enum PreviewMessage {
    BlurHashLoaded(uuid::Uuid, BlurHash),

    ThumbnailDownloaded(uuid::Uuid, bytes::Bytes),
    // ThumbnailAllocated(
    //     uuid::Uuid,
    //     Result<image::Allocation, iced::advanced::image::Error>,
    // ),
    ThumbnailLoaded(uuid::Uuid, Image),

    Failed(uuid::Uuid, String),
}

#[derive(Clone, Debug)]
pub struct Image {
    bytes: bytes::Bytes,
    handle: Handle,
    allocation: image::Allocation,
    fade_in: Animation<bool>,
}

#[derive(Clone, Debug)]
pub struct PreviewSource {
    pub id: uuid::Uuid,
    pub url: String,
    pub width: iced::Length,
    pub height: iced::Length,
    pub blur_hash: Option<String>,
}

impl PreviewSource {
    pub fn new(id: uuid::Uuid, url: String) -> Self {
        Self {
            id,
            url,
            width: iced::Length::Fill,
            height: iced::Length::Fill,
            blur_hash: None,
        }
    }

    pub fn blur_hash(mut self, blur_hash: String) -> Self {
        self.blur_hash = Some(blur_hash);
        self
    }

    pub fn url(mut self, url: impl Into<String>) -> Self {
        self.url = url.into();
        self
    }
    pub fn width(mut self, width: iced::Length) -> Self {
        self.width = width;
        self
    }
    pub fn height(mut self, height: iced::Length) -> Self {
        self.height = height;
        self
    }

    // pub fn upgrade(self) -> Task<PreviewMessage> {
    //     let sip = iced::task::sipper(async move |mut sender| {
    //         let bytes = fut.await;
    //         let handle = Handle::from_bytes(bytes.clone());
    //         let allocation = image::allocate(handle);
    //         let image = Image {
    //             bytes,
    //             handle,
    //             allocation,
    //             fade_in: Animation::new(false),
    //         };
    //         let _ = sender.send(image).await;
    //     });
    //     // iced::Task::sip(sip, ||)
    // }
}

impl ImageCache {
    pub fn upgrade(&self, source: PreviewSource) -> iced::Task<PreviewMessage> {
        let downloader = self.downloader.clone();
        let sipper = iced::task::sipper(async move |mut sender| {
            if let Some(blur_hash_str) = source.blur_hash {
                let blur_hash = BlurHash::new(&blur_hash_str);
                let _ = sender.send(blur_hash).await;
            }

            let bytes = downloader.download(&source.url).await?;
            reqwest::Result::<bytes::Bytes>::Ok(bytes)
        });
        iced::Task::sip(
            sipper,
            move |progress| PreviewMessage::BlurHashLoaded(source.id, progress),
            move |output: reqwest::Result<bytes::Bytes>| match output {
                Ok(bytes) => PreviewMessage::ThumbnailDownloaded(source.id, bytes),
                Err(e) => PreviewMessage::Failed(source.id, e.to_string()),
            },
        )
        .then(|message| match message {
            PreviewMessage::ThumbnailDownloaded(id, bytes) => {
                let handle = Handle::from_bytes(bytes.clone());
                let allocation = image::allocate(&handle);
                allocation.map(move |output| match output {
                    Ok(allocation) => {
                        let image = Image {
                            bytes: bytes.clone(),
                            handle: handle.clone(),
                            allocation,
                            fade_in: Animation::new(false),
                        };
                        PreviewMessage::ThumbnailLoaded(id, image)
                    }
                    Err(e) => PreviewMessage::Failed(id, e.to_string()),
                })
            }
            other => iced::Task::done(other),
        })
    }
}
