use chrono::{DateTime, Utc};
use data::buffer::RightAlignmentWidths;
use data::dashboard::BufferAction;
use data::target::Target;
use data::user::Nick;
use data::{
    Config, Image, Preview, User, buffer, client, history, message, preview,
};
use iced::advanced::text;
use iced::widget::{Space, column, container, row, space};
use iced::{Color, Length, Size, Task, padding};

use super::{context_menu, input_view, scroll_view};
use crate::widget::user_display::UserDisplay;
use crate::widget::{Element, message_content, selectable_text};
use crate::window::Window;
use crate::{Theme, font, theme};

fn row_with_timestamp<'a>(
    timestamp: Option<Element<'a, scroll_view::Message>>,
    content: Element<'a, scroll_view::Message>,
) -> Element<'a, scroll_view::Message> {
    container(row![timestamp, selectable_text(" "), content]).into()
}

fn row_with_timestamp_and_nick<'a>(
    timestamp: Option<Element<'a, scroll_view::Message>>,
    nick: Element<'a, scroll_view::Message>,
    content: Element<'a, scroll_view::Message>,
) -> Element<'a, scroll_view::Message> {
    container(row![
        timestamp,
        selectable_text(" "),
        nick,
        selectable_text(" "),
        content
    ])
    .into()
}

#[derive(Debug, Clone)]
pub enum Message {
    ScrollView(scroll_view::Message),
    InputView(input_view::Message),
    FilehostUploadDone { id: u32, url: Option<String> },
    FilesDropped(Vec<std::path::PathBuf>),
}

pub enum Event {
    ContextMenu(context_menu::Event),
    OpenBuffers(data::server::Server, Vec<(Target, BufferAction)>),
    OpenInternalBuffer(buffer::Internal),
    OpenServer(String),
    Reconnect(data::server::Server),
    LeaveBuffers(Vec<Target>, Option<String>),
    History(Task<history::manager::Message>),
    MarkAsRead(history::Kind),
    OpenUrl(String),
    ImagePreview(Image),
    ExpandMessage(DateTime<Utc>, message::Hash),
    ContractMessage(DateTime<Utc>, message::Hash),
    InputSent {
        history_task: Task<history::manager::Message>,
        open_buffers: Vec<(Target, BufferAction)>,
    },
    FilehostUpload {
        server: data::server::Server,
        target: Option<Target>,
        file_paths: Vec<std::path::PathBuf>,
        upload_ids: Vec<u32>,
        abort_registrations: Vec<futures::future::AbortRegistration>,
    },
}

pub fn view<'a>(
    state: &'a Server,
    clients: &'a data::client::Map,
    history: &'a history::Manager,
    previews: &'a preview::Collection,
    config: &'a Config,
    theme: &'a Theme,
    is_focused: bool,
) -> Element<'a, Message> {
    let chantypes = clients.get_server_chantypes_or_default(&state.server);
    let prefix = clients.get_server_prefix_or_default(&state.server);
    let casemapping = clients.get_server_casemapping_or_default(&state.server);
    let our_nick: Option<data::user::NickRef<'_>> =
        clients.nickname(&state.server);
    let our_user = our_nick.map(|our_nick| User::from(Nick::from(our_nick)));
    let registry = clients.get_registry(&state.server);

    let messages = container(
        scroll_view::view(
            &state.scroll_view,
            scroll_view::Kind::Server(&state.server),
            history,
            None,
            Option::<fn(&Preview, &message::Source) -> bool>::None,
            None,
            0.0,
            config,
            theme,
            move |message: &'a data::Message,
                  right_alignment_widths: Option<RightAlignmentWidths>,
                  _,
                  hide_nickname| {
                let timestamp = config
                    .buffer
                    .format_timestamp(&message.server_time)
                    .map(|timestamp| {
                        context_menu::timestamp(
                            selectable_text(timestamp)
                                .font_maybe(
                                    theme::font_style::timestamp(theme)
                                        .map(font::get),
                                )
                                .style(theme::selectable_text::timestamp),
                            &message.server_time,
                            config,
                            theme,
                        )
                        .map(scroll_view::Message::ContextMenu)
                    });

                match message.target.source() {
                    message::Source::Server(server) => {
                        let text_content = message_content(
                            &message.content,
                            &[],
                            &state.server,
                            registry,
                            chantypes,
                            casemapping,
                            theme,
                            scroll_view::Message::Link,
                            None,
                            move |theme| {
                                theme::selectable_text::server(
                                    theme,
                                    server.as_ref(),
                                )
                            },
                            move |theme| {
                                theme::font_style::server(
                                    theme,
                                    server.as_ref(),
                                )
                            },
                            Option::<fn(Color) -> Color>::None,
                            None,
                            config,
                        );

                        Some(context_menu::message(
                            row_with_timestamp(timestamp, text_content),
                            message,
                            vec![],
                            false,
                            false,
                            false,
                            config,
                            theme,
                        ))
                    }
                    message::Source::Internal(
                        message::source::Internal::Status(status),
                    ) => {
                        let content = message_content(
                            &message.content,
                            &[],
                            &state.server,
                            registry,
                            chantypes,
                            casemapping,
                            theme,
                            scroll_view::Message::Link,
                            None,
                            move |theme| {
                                theme::selectable_text::status(theme, *status)
                            },
                            move |theme| {
                                theme::font_style::status(theme, *status)
                            },
                            Option::<fn(Color) -> Color>::None,
                            None,
                            config,
                        );

                        Some(row_with_timestamp(timestamp, content))
                    }
                    message::Source::User(user) => {
                        let user_display = UserDisplay::new(
                            user,
                            config.buffer.nickname.show_access_levels,
                            config.buffer.nickname.show_bot_icon,
                            registry,
                            &config.display.nickname,
                            config.buffer.nickname.truncate,
                            config.display.truncation_character,
                            Some(&config.buffer.nickname.brackets),
                            true,
                        );

                        let nick: Element<_> = if hide_nickname {
                            let width = match config.buffer.nickname.alignment {
                                data::buffer::Alignment::Left
                                | data::buffer::Alignment::Top => {
                                    user_display.width(config)
                                }
                                data::buffer::Alignment::Right => {
                                    if let Some(right_alignment_widths) =
                                        right_alignment_widths
                                    {
                                        right_alignment_widths.middle
                                    } else {
                                        user_display.width(config)
                                    }
                                }
                            };

                            Space::new().width(width).into()
                        } else {
                            let mut nick_text = user_display.into_element(
                                user, false, false, None, None, false, true,
                                theme, config,
                            );

                            if let Some(right_alignment_widths) =
                                right_alignment_widths
                            {
                                nick_text = container(nick_text)
                                    .width(right_alignment_widths.middle)
                                    .align_x(text::Alignment::Right)
                                    .into();
                            }

                            context_menu::user(
                                nick_text,
                                &state.server,
                                prefix,
                                None,
                                registry,
                                previews,
                                user,
                                None,
                                None,
                                config,
                                theme,
                                &config.buffer.nickname.click,
                            )
                            .map(scroll_view::Message::ContextMenu)
                        };

                        let rerouted_private = message.is_rerouted();

                        let content = message_content(
                            &message.content,
                            &[],
                            &state.server,
                            registry,
                            chantypes,
                            casemapping,
                            theme,
                            scroll_view::Message::Link,
                            None,
                            move |theme| {
                                if rerouted_private {
                                    theme::selectable_text::tertiary(theme)
                                } else {
                                    theme::selectable_text::default(theme)
                                }
                            },
                            theme::font_style::primary,
                            Option::<fn(Color) -> Color>::None,
                            None,
                            config,
                        );

                        Some(context_menu::message(
                            row_with_timestamp_and_nick(
                                timestamp, nick, content,
                            ),
                            message,
                            vec![],
                            false,
                            false,
                            false,
                            config,
                            theme,
                        ))
                    }
                    _ => None,
                }
            },
            clients.get_registry(&state.server),
        )
        .map(Message::ScrollView),
    )
    .height(Length::Fill)
    .padding(padding::left(8).right(8));

    let show_text_input = match config.buffer.text_input.visibility {
        data::config::buffer::text_input::Visibility::Focused => is_focused,
        data::config::buffer::text_input::Visibility::Always => true,
    };

    let filehost_url = clients.get_filehost(&state.server);

    let text_input = show_text_input.then(|| {
        column![
            space::vertical().height(4),
            input_view::view(
                &state.input_view,
                our_user.as_ref(),
                None,
                &state.server,
                registry,
                config,
                theme,
                filehost_url,
            )
            .map(Message::InputView)
        ]
        .width(Length::Fill)
    });

    let scrollable = column![messages, text_input,].height(Length::Fill);

    container(scrollable)
        .width(Length::Fill)
        .height(Length::Fill)
        .into()
}

#[derive(Debug, Clone)]
pub struct Server {
    pub buffer: buffer::Upstream,
    pub server: data::server::Server,
    pub scroll_view: scroll_view::State,
    pub input_view: input_view::State,
}

impl Server {
    pub fn new(
        server: data::server::Server,
        clients: &client::Map,
        history: &history::Manager,
        pane_size: Size,
        config: &Config,
    ) -> Self {
        let buffer = buffer::Upstream::Server(server.clone());

        Self {
            input_view: input_view::State::new(
                history.input(&buffer),
                &buffer,
                clients,
                history,
                config,
            ),
            buffer,
            server,
            scroll_view: scroll_view::State::new(pane_size, config),
        }
    }

    pub fn update(
        &mut self,
        message: Message,
        clients: &mut data::client::Map,
        history: &mut history::Manager,
        main_window: &Window,
        config: &Config,
    ) -> (Task<Message>, Option<Event>) {
        match message {
            Message::ScrollView(message) => {
                let (command, event) = self.scroll_view.update(
                    message,
                    false,
                    scroll_view::Kind::Server(&self.server),
                    Some(&self.buffer),
                    history,
                    clients,
                    config,
                );

                let event = event.and_then(|event| match event {
                    scroll_view::Event::ContextMenu(event) => {
                        Some(Event::ContextMenu(event))
                    }
                    scroll_view::Event::OpenBuffer(
                        server,
                        target,
                        buffer_action,
                    ) => Some(Event::OpenBuffers(
                        server,
                        vec![(target, buffer_action)],
                    )),
                    scroll_view::Event::GoToMessage(_, _, _) => None,
                    scroll_view::Event::RequestOlderChatHistory => None,
                    scroll_view::Event::PreviewChanged => None,
                    scroll_view::Event::HidePreview(..) => None,
                    scroll_view::Event::MarkAsRead => {
                        history::Kind::from_buffer(data::Buffer::Upstream(
                            self.buffer.clone(),
                        ))
                        .map(Event::MarkAsRead)
                    }
                    scroll_view::Event::OpenUrl(url) => {
                        Some(Event::OpenUrl(url))
                    }
                    scroll_view::Event::ImagePreview(image) => {
                        Some(Event::ImagePreview(image))
                    }
                    scroll_view::Event::ExpandMessage(server_time, hash) => {
                        Some(Event::ExpandMessage(server_time, hash))
                    }
                    scroll_view::Event::ContractMessage(server_time, hash) => {
                        Some(Event::ContractMessage(server_time, hash))
                    }
                });

                (command.map(Message::ScrollView), event)
            }
            Message::InputView(message) => {
                let (command, event) = self.input_view.update(
                    message,
                    &self.buffer,
                    clients,
                    history,
                    main_window,
                    config,
                );
                let command = command.map(Message::InputView);

                match event {
                    Some(input_view::Event::InputSent {
                        history_task,
                        open_buffers,
                    }) => (
                        Task::batch(vec![
                            command,
                            self.scroll_view
                                .scroll_to_end(config)
                                .map(Message::ScrollView),
                        ]),
                        Some(Event::InputSent {
                            history_task,
                            open_buffers,
                        }),
                    ),
                    Some(input_view::Event::OpenBuffers {
                        server,
                        targets,
                    }) => (command, Some(Event::OpenBuffers(server, targets))),
                    Some(input_view::Event::LeaveBuffers {
                        targets,
                        reason,
                    }) => (command, Some(Event::LeaveBuffers(targets, reason))),
                    Some(input_view::Event::Cleared { history_task }) => {
                        (command, Some(Event::History(history_task)))
                    }
                    Some(input_view::Event::OpenInternalBuffer(buffer)) => {
                        (command, Some(Event::OpenInternalBuffer(buffer)))
                    }
                    Some(input_view::Event::OpenServer(server)) => {
                        (command, Some(Event::OpenServer(server)))
                    }
                    Some(input_view::Event::Reconnect(server)) => {
                        (command, Some(Event::Reconnect(server)))
                    }
                    Some(input_view::Event::FilehostUpload {
                        server,
                        target,
                        file_paths,
                        upload_ids,
                        abort_registrations,
                    }) => (
                        command,
                        Some(Event::FilehostUpload {
                            server,
                            target,
                            file_paths,
                            upload_ids,
                            abort_registrations,
                        }),
                    ),
                    None => (command, None),
                }
            }
            Message::FilehostUploadDone { id, url } => {
                let (task, _) = self.input_view.update(
                    input_view::Message::FilehostUploadDone { id, url },
                    &self.buffer,
                    clients,
                    history,
                    main_window,
                    config,
                );
                (task.map(Message::InputView), None)
            }
            Message::FilesDropped(paths) => {
                let (task, event) = self.input_view.update(
                    input_view::Message::FilesSelected(paths),
                    &self.buffer,
                    clients,
                    history,
                    main_window,
                    config,
                );
                (
                    task.map(Message::InputView),
                    event.and_then(|e| match e {
                        input_view::Event::FilehostUpload {
                            server,
                            target,
                            file_paths,
                            upload_ids,
                            abort_registrations,
                        } => Some(Event::FilehostUpload {
                            server,
                            target,
                            file_paths,
                            upload_ids,
                            abort_registrations,
                        }),
                        _ => None,
                    }),
                )
            }
        }
    }

    pub fn focus(&self) -> Task<Message> {
        self.input_view.focus().map(Message::InputView)
    }

    pub fn reset(&mut self) {
        self.input_view.reset();
    }
}
