use gtk::prelude::*;
use lemmy_api_common::{
    lemmy_db_views::structs::{CommentView, PostView},
    post::GetPostResponse,
};
use relm4::{factory::FactoryVecDeque, prelude::*};
use relm4_components::web_image::WebImage;

use crate::{
    api,
    dialogs::{
        editor::{DialogMsg, EditorData, EditorDialog, EditorOutput, EditorType},
        report_dialog::{ReportDialog, ReportDialogInput},
    },
    settings,
    util::{self, get_web_image_msg, get_web_image_url, markdown_to_pango_markup},
};

use super::{
    comment_row::CommentRow,
    voting_row::{VotingRowInput, VotingRowModel, VotingStats},
};

pub struct PostPage {
    info: GetPostResponse,
    image: Controller<WebImage>,
    creator_avatar: Controller<WebImage>,
    community_avatar: Controller<WebImage>,
    comments: FactoryVecDeque<CommentRow>,
    #[allow(dead_code)]
    create_comment_dialog: Controller<EditorDialog>,
    report_post_dialog: Controller<ReportDialog>,
    voting_row: Controller<VotingRowModel>,
    thumbnail_height: i32,
}

#[derive(Debug)]
pub enum PostPageInput {
    UpdatePost(GetPostResponse),
    DoneFetchComments(Vec<CommentView>),
    OpenPerson,
    OpenCommunity,
    OpenLink,
    OpenImage,
    OpenCreateCommentDialog,
    CreateCommentRequest(EditorData),
    EditPostRequest(EditorData),
    CreatedComment(CommentView),
    OpenEditPostDialog,
    ToggleSaved,
    UpdateSaved(bool),
    DeletePost,
    DoneEditPost(PostView),
    PassAppMessage(crate::AppMsg),
    ShowReportDialog,
}

#[relm4::component(pub)]
impl SimpleComponent for PostPage {
    type Init = GetPostResponse;
    type Input = PostPageInput;
    type Output = crate::AppMsg;

    view! {
        gtk::ScrolledWindow {
            gtk::Box {
                set_orientation: gtk::Orientation::Vertical,
                set_vexpand: false,
                set_hexpand: true,
                set_margin_all: 10,

                #[local_ref]
                image -> gtk::Box {
                    #[watch]
                    set_height_request: model.thumbnail_height,
                    set_margin_bottom: 20,
                    set_margin_top: 20,
                    #[watch]
                    set_visible: model.info.post_view.post.thumbnail_url.is_some(),
                    add_controller = gtk::GestureClick {
                        connect_pressed[sender] => move |_, _, _, _| {
                            sender.input(PostPageInput::OpenImage);
                        }
                    },
                },
                gtk::Label {
                    #[watch]
                    set_text: &model.info.post_view.post.name,
                    set_wrap: true,
                    add_css_class: "font-very-bold",
                    add_controller = gtk::GestureClick {
                        connect_pressed[sender] => move |_, _, _, _| {
                            sender.input(PostPageInput::OpenLink);
                        }
                    },
                },
                gtk::Label {
                    #[watch]
                    set_markup: &markdown_to_pango_markup(model.info.post_view.post.body.clone().unwrap_or("".to_string())),
                    set_wrap: true,
                    set_margin_top: 10,
                    set_margin_start: 15,
                    set_margin_end: 15,
                    set_use_markup: true,
                },

                gtk::Box {
                    set_orientation: gtk::Orientation::Horizontal,
                    set_margin_top: 10,
                    set_spacing: 10,
                    set_vexpand: false,
                    set_halign: gtk::Align::Center,

                    gtk::Label {
                        set_text: "posted by"
                    },

                    #[local_ref]
                    creator_avatar -> gtk::Box {
                        set_hexpand: false,
                        set_margin_start: 10,
                        set_visible: model.info.post_view.creator.avatar.is_some(),
                    },

                    gtk::Button {
                        #[watch]
                        set_label: &model.info.post_view.creator.name,
                        connect_clicked => PostPageInput::OpenPerson,
                    },

                    gtk::Label {
                        set_text: " in "
                    },

                    #[local_ref]
                    community_avatar -> gtk::Box {
                        set_hexpand: false,
                        set_visible: model.info.community_view.community.icon.is_some(),
                    },

                    gtk::Button {
                        #[watch]
                        set_label: &model.info.community_view.community.title,
                        connect_clicked => PostPageInput::OpenCommunity,
                    },

                    gtk::Button {
                        set_icon_name: "document-edit",
                        connect_clicked => PostPageInput::OpenEditPostDialog,
                        set_margin_start: 5,
                        #[watch]
                        set_visible: model.info.post_view.creator.id.0 == settings::get_current_account().id,
                    },

                    gtk::Button {
                        set_icon_name: "edit-delete",
                        connect_clicked => PostPageInput::DeletePost,
                        #[watch]
                        set_visible: model.info.post_view.creator.id.0 == settings::get_current_account().id,
                    }
                },

                gtk::Box {
                    set_orientation: gtk::Orientation::Horizontal,
                    set_margin_top: 15,
                    set_margin_bottom: 10,
                    set_halign: gtk::Align::Center,

                    gtk::Label {
                        set_margin_end: 15,
                        set_label: &util::format_elapsed_time(model.info.post_view.post.published),
                    },
                    #[local_ref]
                    voting_row -> gtk::Box {
                        set_margin_end: 10,
                    },
                    gtk::Label {
                        #[watch]
                        set_text: &format!("{} comments", model.info.post_view.counts.comments),
                        set_margin_start: 10,
                    },
                    gtk::Button {
                        set_label: "Comment",
                        set_margin_start: 10,
                        connect_clicked => PostPageInput::OpenCreateCommentDialog,
                        #[watch]
                        set_visible: settings::get_current_account().jwt.is_some(),
                    },
                    gtk::ToggleButton {
                        set_icon_name: "bookmark-new",
                        set_margin_start: 10,
                        connect_clicked => PostPageInput::ToggleSaved,
                        set_visible: settings::get_current_account().jwt.is_some(),
                        #[watch]
                        set_active: model.info.post_view.saved,
                    },
                    gtk::Button {
                        set_icon_name: "action-unavailable",
                        set_margin_start: 10,
                        connect_clicked => PostPageInput::ShowReportDialog,
                        set_visible: settings::get_current_account().jwt.is_some(),
                    },
                },

                #[local_ref]
                comments -> gtk::Box {
                    set_orientation: gtk::Orientation::Vertical,
                }
            }
        }
    }

    fn init(
        init: Self::Init,
        root: &Self::Root,
        sender: relm4::ComponentSender<Self>,
    ) -> relm4::ComponentParts<Self> {
        let image = WebImage::builder().launch("".to_string()).detach();
        let comments = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
        let creator_avatar = WebImage::builder().launch("".to_string()).detach();
        let community_avatar = WebImage::builder().launch("".to_string()).detach();
        let create_comment_dialog = EditorDialog::builder()
            .transient_for(root)
            .launch(EditorType::Comment)
            .forward(sender.input_sender(), |msg| match msg {
                EditorOutput::CreateRequest(comment, _) => {
                    PostPageInput::CreateCommentRequest(comment)
                }
                EditorOutput::EditRequest(post, _) => PostPageInput::EditPostRequest(post),
            });
        let voting_row = VotingRowModel::builder()
            .launch(VotingStats::default())
            .detach();
        let report_post_dialog = ReportDialog::builder()
            .transient_for(root)
            .launch((None, None))
            .detach();

        let model = PostPage {
            info: init,
            image,
            comments,
            creator_avatar,
            community_avatar,
            create_comment_dialog,
            report_post_dialog,
            voting_row,
            thumbnail_height: 400,
        };

        let image = model.image.widget();
        let comments = model.comments.widget();
        let creator_avatar = model.creator_avatar.widget();
        let community_avatar = model.community_avatar.widget();
        let voting_row = model.voting_row.widget();
        let widgets = view_output!();

        ComponentParts { model, widgets }
    }

    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
        match message {
            PostPageInput::UpdatePost(post) => {
                match relm4::main_application().active_window() {
                    Some(window) => {
                        self.thumbnail_height = window.allocated_width() / 2;
                    }
                    None => unreachable!(),
                }

                self.info = post.clone();

                self.image
                    .emit(get_web_image_msg(post.post_view.post.thumbnail_url));
                self.community_avatar
                    .emit(get_web_image_msg(post.community_view.community.icon));
                self.creator_avatar
                    .emit(get_web_image_msg(post.post_view.creator.avatar));

                self.voting_row
                    .emit(VotingRowInput::UpdateStats(VotingStats::from_post(
                        post.post_view.counts.clone(),
                        post.post_view.my_vote,
                    )));
                self.comments.guard().clear();

                std::thread::spawn(move || {
                    if post.post_view.counts.comments == 0 {
                        return;
                    }
                    let comments = api::post::get_comments(post.post_view.post.id);
                    if let Ok(comments) = comments {
                        sender.input(PostPageInput::DoneFetchComments(comments));
                    }
                });
            }
            PostPageInput::DoneFetchComments(comments) => {
                for comment in comments {
                    self.comments.guard().push_back(comment);
                }
            }
            PostPageInput::OpenPerson => {
                let person_id = self.info.post_view.creator.id;
                sender
                    .output_sender()
                    .emit(crate::AppMsg::OpenPerson(person_id));
            }
            PostPageInput::OpenCommunity => {
                let community_id = self.info.community_view.community.id;
                sender
                    .output_sender()
                    .emit(crate::AppMsg::OpenCommunity(community_id));
            }
            PostPageInput::OpenLink => {
                let post = self.info.post_view.post.clone();
                let mut link = get_web_image_url(post.url);
                if link.is_empty() {
                    link = get_web_image_url(post.embed_video_url);
                }
                if !link.is_empty() {
                    gtk::show_uri(None::<&relm4::gtk::Window>, &link, 0);
                }
            }
            PostPageInput::OpenImage => {
                let link = get_web_image_url(self.info.post_view.post.thumbnail_url.clone());
                if !link.is_empty() {
                    gtk::show_uri(None::<&relm4::gtk::Window>, &link, 0);
                }
            }
            PostPageInput::OpenCreateCommentDialog => {
                let sender = self.create_comment_dialog.sender();
                sender.emit(DialogMsg::UpdateType(EditorType::Comment, true));
                sender.emit(DialogMsg::Show);
            }
            PostPageInput::CreatedComment(comment) => {
                self.comments.guard().push_front(comment);
            }
            PostPageInput::CreateCommentRequest(post) => {
                let id = self.info.post_view.post.id;
                std::thread::spawn(move || {
                    let message = match api::comment::create_comment(id, post.body, None) {
                        Ok(comment) => Some(PostPageInput::CreatedComment(comment.comment_view)),
                        Err(err) => {
                            println!("{}", err);
                            None
                        }
                    };
                    if let Some(message) = message {
                        sender.input(message)
                    };
                });
            }
            PostPageInput::ToggleSaved => {
                let post_id = self.info.post_view.post.id;
                let new_state = !self.info.post_view.saved;
                std::thread::spawn(move || match api::post::save_post(post_id, new_state) {
                    Ok(post) => sender.input(PostPageInput::UpdateSaved(post.post_view.saved)),
                    Err(err) => println!("{}", err),
                });
            }
            PostPageInput::UpdateSaved(is_saved) => {
                self.info.post_view.saved = is_saved;
            }
            PostPageInput::DeletePost => {
                let post_id = self.info.post_view.post.id;
                std::thread::spawn(move || {
                    let _ = api::post::delete_post(post_id);
                    sender.output_sender().emit(crate::AppMsg::OpenPosts);
                });
            }
            PostPageInput::OpenEditPostDialog => {
                let url = match self.info.post_view.post.url.clone() {
                    Some(url) => url.to_string(),
                    None => String::from(""),
                };
                let data = EditorData {
                    name: self.info.post_view.post.name.clone(),
                    body: self
                        .info
                        .post_view
                        .post
                        .body
                        .clone()
                        .unwrap_or(String::from("")),
                    url: reqwest::Url::parse(&url).ok(),
                };
                let sender = self.create_comment_dialog.sender();
                sender.emit(DialogMsg::UpdateData(data));
                sender.emit(DialogMsg::UpdateType(EditorType::Post, false));
                sender.emit(DialogMsg::Show);
            }
            PostPageInput::EditPostRequest(post) => {
                let id = self.info.post_view.post.id.0;
                std::thread::spawn(move || {
                    let message = match api::post::edit_post(post.name, post.url, post.body, id) {
                        Ok(post) => Some(PostPageInput::DoneEditPost(post.post_view)),
                        Err(err) => {
                            println!("{}", err);
                            None
                        }
                    };
                    if let Some(message) = message {
                        sender.input(message)
                    };
                });
            }
            PostPageInput::DoneEditPost(post) => {
                self.info.post_view = post;
            }
            PostPageInput::PassAppMessage(message) => {
                sender.output_sender().emit(message);
            }
            PostPageInput::ShowReportDialog => {
                let message = ReportDialogInput::UpdateId(Some(self.info.post_view.post.id), None);
                self.report_post_dialog.sender().emit(message);
                self.report_post_dialog
                    .sender()
                    .emit(ReportDialogInput::Show);
            }
        }
    }
}
