use dioxus::prelude::*;

use crate::domain::entities::site::{Block, PageContent, Post};

use super::{meta, server::get_posts};

#[component]
pub fn Page() -> Element {
    let page = meta::page();
    let info = page.data.info;

    rsx! {
        h2 { "Page {info.title}" }
        match page.data.content {
            PageContent::Single { content } => rsx! {
                PostElement { post: content }
            },
            PageContent::Collection { kind: _, collection_id } => {
                rsx! {
                    SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "..." },
                        Collection { collection_id }
                    }
                }
            }
        }
    }
}

#[component]
pub fn Collection(collection_id: String) -> Element {
    let site = meta::site();
    let posts =
        use_server_future(move || get_posts(site.info.domain.clone(), collection_id.clone()))?
            .suspend()?;

    let result = match &*posts.read() {
        Ok(posts) => rsx! {
            for post in posts.data.clone() {
                PostElement { post }
            }
        },
        Err(err) => {
            rsx! {
                h1 { "FATAL ERROR: {err}" }
            }
        }
    };

    result
}

#[component]
pub fn PostElement(post: Post) -> Element {
    rsx! {
        for block in post.blocks {
            BlockElement { block }
        }
    }
}

#[component]
pub fn BlockElement(block: Block) -> Element {
    match block {
        Block::Text { text } => rsx! {
            p { "{text}" }
        },
        Block::Gallery { images } => rsx! {
            for image in images {
                img { src: "{image.src}", alt: "{image.caption}" }
            }
        },
    }
}

#[cfg(test)]
mod tests {
    use meta::SiteContext;

    use crate::{
        domain::entities::{
            cursor::Paginated,
            site::{Image, SiteInfo},
        },
        outbound::services::site::{MockSiteService, SiteServiceProvider},
    };

    use super::*;

    #[test]
    fn block_renders_text() {
        let text = "Hello, world!";
        let block = Block::Text {
            text: text.to_string(),
        };
        let element = rsx! {
            BlockElement { block }
        };
        let elem_str = dioxus::ssr::render_element(element);
        assert!(elem_str.contains(text));
    }

    #[test]
    fn block_gallery_renders_images() {
        let images = vec![
            Image {
                src: "https://example.com/image1.jpg".to_string(),
                caption: "Image 1".to_string(),
            },
            Image {
                src: "https://example.com/image2.jpg".to_string(),
                caption: "Image 2".to_string(),
            },
        ];
        let block = Block::Gallery { images };
        let element = rsx! {
            BlockElement { block }
        };
        let elem_str = dioxus::ssr::render_element(element);
        assert!(elem_str.contains("<img"));
        assert!(elem_str.contains("https://example.com/image1.jpg"));
        assert!(elem_str.contains("https://example.com/image2.jpg"));
    }

    #[test]
    fn post_renders_blocks() {
        let blocks = vec![
            Block::Text {
                text: "Hello, world!".to_string(),
            },
            Block::Text {
                text: "Something else!".to_string(),
            },
        ];
        let post = Post { blocks };
        let element = rsx! {
            PostElement { post }
        };
        let elem_str = dioxus::ssr::render_element(element);
        assert!(elem_str.contains("Hello, world!"));
        assert!(elem_str.contains("Something else!"));
    }

    #[test]
    fn collection_renders_posts() {
        let mut app = VirtualDom::new(|| {
            rsx! {
                Collection { collection_id: "".to_string() }
            }
        });

        app.provide_root_context(SiteContext {
            info: SiteInfo {
                title: "test".to_string(),
                domain: "test".to_string(),
                pages: vec![],
            },
        });

        let mut mock_service = MockSiteService::new();
        mock_service
            .expect_get_posts()
            .times(1)
            .returning(move |_, _, _| {
                Box::pin(async {
                    Ok(Paginated {
                        data: vec![
                            Post {
                                blocks: vec![Block::Text {
                                    text: "Hello, world!".to_string(),
                                }],
                            },
                            Post {
                                blocks: vec![Block::Text {
                                    text: "Something else!".to_string(),
                                }],
                            },
                        ],
                        next: None,
                    })
                })
            });

        server_context().insert(SiteServiceProvider::with(mock_service));

        app.rebuild_in_place();
        let elem_str = dioxus::ssr::render(&app);
        println!("elem_str: {elem_str}");
        assert!(elem_str.contains("Hello, world!"));
        assert!(elem_str.contains("Something else!"));
    }
}