use super::common;
use crate::{
    appimage_key, flatpak_key, json_bool, json_string, package_key, remaining_update_count,
    webapp_key, Message, SoftwareApp, UpdateTarget,
};
use cosmic::iced::{Alignment, Length};
use cosmic::{theme, widget, Element};

pub fn view(app: &SoftwareApp) -> Element<'_, Message> {
    let spacing = theme::active().cosmic().spacing;
    let remaining = remaining_update_count(
        app.updates_data.as_ref(),
        &app.updates_completed,
        app.updates_image_done,
    );

    let mut column = widget::column()
        .padding([0, spacing.space_s, spacing.space_m, spacing.space_s])
        .spacing(spacing.space_xs)
        .width(Length::Fill);

    column = column.push(header(app, remaining));

    if !app.updates_status.is_empty() {
        column = column.push(widget::text::caption(&app.updates_status));
    }

    if app.updates_loading && app.updates_data.is_none() {
        return column.push(common::loading_page("Checking updates...")).into();
    }

    let Some(data) = app.updates_data.as_ref() else {
        return column
            .push(empty_state(
                "No update data yet.",
                "The tray daemon normally fills this in. You can also check manually.",
            ))
            .into();
    };

    if remaining == 0 {
        column = column.push(empty_state(
            if data.reboot_required || app.updates_image_done {
                "Updates are staged."
            } else {
                "System is up to date."
            },
            if data.reboot_required || app.updates_image_done {
                "Reboot to boot into the updated system image."
            } else {
                "No package, Flatpak, AppImage, web app, or image updates are pending."
            },
        ));
        return column.into();
    }

    if data.image_available && !app.updates_image_done {
        column = column.push(section_title("System Image"));
        column = column.push(image_card(data, app.updates_updating, app.updates_image_progress));
    }

    let app_updates = application_updates(app, data);
    if !app_updates.is_empty() {
        column = column.push(section_header(
            "Applications",
            app_updates.len(),
        ));
        for row in app_updates {
            column = column.push(row);
        }
    }

    let runtime_updates = flatpak_runtime_updates(app, data);
    if !runtime_updates.is_empty() {
        column = column.push(section_header(
            "Runtimes / Add-ons",
            runtime_updates.len(),
        ));
        for row in runtime_updates {
            column = column.push(row);
        }
    }

    let overlay_updates = overlay_dependency_updates(app, data);
    if !overlay_updates.is_empty() {
        column = column.push(section_header(
            "Overlay Dependencies",
            overlay_updates.len(),
        ));
        for row in overlay_updates {
            column = column.push(row);
        }
    }

    column.into()
}

fn header(app: &SoftwareApp, remaining: usize) -> Element<'_, Message> {
    let spacing = theme::active().cosmic().spacing;
    let mut row = widget::row()
        .spacing(spacing.space_xs)
        .align_y(Alignment::Center)
        .width(Length::Fill);

    row = row.push(
        widget::column::with_children(vec![
            widget::text::title2("Updates").into(),
            widget::text::caption(if remaining == 1 {
                "1 update available".to_string()
            } else {
                format!("{remaining} updates available")
            })
            .into(),
        ])
        .width(Length::Fill),
    );

    let check_button: Element<_> = if app.updates_checking {
        widget::button::standard("Checking...").into()
    } else {
        widget::button::standard("Check for Updates")
            .on_press(Message::CheckUpdates)
            .into()
    };
    row = row.push(check_button);

    if remaining > 0 {
        let update_button: Element<_> = if app.updates_updating {
            widget::button::suggested("Updating...").into()
        } else {
            widget::button::suggested("Update All")
                .on_press(Message::UpdateTarget(UpdateTarget::All))
                .into()
        };
        row = row.push(update_button);
    }

    if app.updates_image_done
        || app
            .updates_data
            .as_ref()
            .map(|data| data.reboot_required)
            .unwrap_or(false)
    {
        row = row.push(
            widget::button::suggested("Reboot")
                .on_press(Message::RebootSystem),
        );
    }

    row.into()
}

fn empty_state<'a>(title: &'a str, body: &'a str) -> Element<'a, Message> {
    let spacing = theme::active().cosmic().spacing;
    widget::container(
        widget::column::with_children(vec![
            widget::text::heading(title).into(),
            widget::text::body(body).into(),
        ])
        .spacing(spacing.space_xxs),
    )
    .width(Length::Fill)
    .padding(spacing.space_s)
    .class(theme::Container::Card)
    .into()
}

fn section_title(title: &'static str) -> Element<'static, Message> {
    widget::text::heading(title).into()
}

fn section_header(title: &'static str, count: usize) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    widget::row::with_children(vec![
        widget::column::with_children(vec![
            widget::text::heading(title).into(),
            widget::text::caption(format!("{count} pending")).into(),
        ])
        .width(Length::Fill)
        .into(),
    ])
    .spacing(spacing.space_xs)
    .align_y(Alignment::Center)
    .into()
}

fn image_card(data: &crate::UpdatesData, busy: bool, progress: f32) -> Element<'_, Message> {
    let spacing = theme::active().cosmic().spacing;
    let current = data.image_info["current_version"]
        .as_str()
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| data.image_info["booted"].as_str().unwrap_or("Current image"));
    let available = data.image_info["available_version"]
        .as_str()
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| data.image_info["available"].as_str().unwrap_or("New image"));
    let update_type = data.image_info["type"].as_str().unwrap_or("hotfix");
    let cur_date = current.rsplit_once('.').map(|(_, d)| d).unwrap_or("");
    let avail_date = available.rsplit_once('.').map(|(_, d)| d).unwrap_or("");
    let is_hotfix = update_type != "migrate" && !cur_date.is_empty() && cur_date == avail_date;
    let action: Element<_> = if busy {
        widget::button::suggested("Updating...").into()
    } else {
        widget::button::suggested(if is_hotfix { "Apply Hotfix" } else { "Update" })
            .on_press(Message::UpdateTarget(UpdateTarget::Image))
            .into()
    };

    let mut inner = widget::column()
        .spacing(spacing.space_xs)
        .width(Length::Fill);

    inner = inner.push(
        widget::row::with_children(vec![
            widget::icon::from_name("software-update-available-symbolic")
                .size(40)
                .into(),
            widget::column::with_children(vec![
                widget::text::body(if is_hotfix { "System hotfix available" } else { "System update available" }).into(),
                widget::text::caption(if is_hotfix {
                    format!("Hotfix for {current}  →  {available}")
                } else {
                    format!("{current}  →  {available}")
                }).into(),
            ])
            .spacing(2)
            .width(Length::Fill)
            .into(),
            action,
        ])
        .spacing(spacing.space_s)
        .align_y(Alignment::Center),
    );

    if busy {
        inner = inner.push(
            widget::progress_bar(0.0..=1.0, progress.max(0.02))
                .girth(8),
        );
    }

    widget::container(inner)
        .width(Length::Fill)
        .padding(spacing.space_s)
        .class(theme::Container::Card)
        .into()
}

fn application_updates<'a>(
    app: &'a SoftwareApp,
    data: &'a crate::UpdatesData,
) -> Vec<Element<'a, Message>> {
    let mut rows = Vec::new();

    for update in &data.packages {
        if !json_bool(update, "gui") {
            continue;
        }
        let Some(name) = json_string(update, "name") else {
            continue;
        };
        if app.updates_completed.contains(&package_key(&name)) {
            continue;
        }
        rows.push(package_row(update, Message::UpdateTarget(UpdateTarget::Package(name)), app.updates_updating));
    }

    for update in &data.flatpak {
        if json_bool(update, "runtime") {
            continue;
        }
        let Some(id) = json_string(update, "id").or_else(|| json_string(update, "app_id")) else {
            continue;
        };
        if app.updates_completed.contains(&flatpak_key(&id)) {
            continue;
        }
        let target = UpdateTarget::FlatpakApp(id);
        rows.push(flatpak_row(update, Message::UpdateTarget(target), app.updates_updating));
    }

    for update in &data.appimages {
        let Some(id) = json_string(update, "id") else {
            continue;
        };
        if app.updates_completed.contains(&appimage_key(&id)) {
            continue;
        }
        let url = json_string(update, "download_url").unwrap_or_default();
        rows.push(appimage_row(
            update,
            Message::UpdateTarget(UpdateTarget::AppImage { id, url }),
            app.updates_updating,
        ));
    }

    for update in &data.webapps {
        let Some(id) = json_string(update, "id") else {
            continue;
        };
        if app.updates_completed.contains(&webapp_key(&id)) {
            continue;
        }
        rows.push(webapp_row(update, Message::UpdateTarget(UpdateTarget::WebApp(id)), app.updates_updating));
    }

    rows
}

fn flatpak_runtime_updates<'a>(
    app: &'a SoftwareApp,
    data: &'a crate::UpdatesData,
) -> Vec<Element<'a, Message>> {
    data.flatpak
        .iter()
        .filter(|update| json_bool(update, "runtime"))
        .filter_map(|update| {
            let id = json_string(update, "id").or_else(|| json_string(update, "app_id"))?;
            if app.updates_completed.contains(&flatpak_key(&id)) {
                return None;
            }
            let branch = json_string(update, "version").unwrap_or_default();
            let target = if update["needs_install"].as_bool().unwrap_or(false) {
                let remote = json_string(update, "remote").unwrap_or_default();
                UpdateTarget::FlatpakRuntimeInstall { remote, app_id: id, branch }
            } else {
                UpdateTarget::FlatpakRuntime { app_id: id, branch }
            };
            Some(flatpak_row(update, Message::UpdateTarget(target), app.updates_updating))
        })
        .collect()
}

fn overlay_dependency_updates<'a>(
    app: &'a SoftwareApp,
    data: &'a crate::UpdatesData,
) -> Vec<Element<'a, Message>> {
    data.packages
        .iter()
        .filter(|update| !json_bool(update, "gui"))
        .filter_map(|update| {
            let name = json_string(update, "name")?;
            if app.updates_completed.contains(&package_key(&name)) {
                None
            } else {
                Some(package_row(
                    update,
                    Message::UpdateTarget(UpdateTarget::Package(name)),
                    app.updates_updating,
                ))
            }
        })
        .collect()
}

fn package_row(update: &serde_json::Value, action: Message, busy: bool) -> Element<'static, Message> {
    let title = json_string(update, "display_name")
        .or_else(|| json_string(update, "name"))
        .unwrap_or_else(|| "Package update".to_string());
    let subtitle = format!(
        "{} -> {}",
        json_string(update, "current_version").unwrap_or_else(|| "installed".to_string()),
        json_string(update, "version").unwrap_or_else(|| "available".to_string())
    );
    update_row(
        title,
        subtitle,
        json_string(update, "icon_path").unwrap_or_default(),
        json_string(update, "icon_url").unwrap_or_default(),
        action,
        busy,
    )
}

fn flatpak_row(update: &serde_json::Value, action: Message, busy: bool) -> Element<'static, Message> {
    let title = json_string(update, "name")
        .or_else(|| json_string(update, "id"))
        .unwrap_or_else(|| "Flatpak update".to_string());
    let subtitle = format!(
        "{}{}",
        json_string(update, "version").unwrap_or_else(|| "Flatpak".to_string()),
        json_string(update, "origin")
            .map(|origin| format!(" from {origin}"))
            .unwrap_or_default()
    );
    update_row(
        title,
        subtitle,
        json_string(update, "icon_path").unwrap_or_default(),
        json_string(update, "icon_url").unwrap_or_default(),
        action,
        busy,
    )
}

fn webapp_row(update: &serde_json::Value, action: Message, busy: bool) -> Element<'static, Message> {
    let title = json_string(update, "name")
        .or_else(|| json_string(update, "id"))
        .unwrap_or_else(|| "Web app update".to_string());
    let subtitle = json_string(update, "summary")
        .or_else(|| json_string(update, "new_last_updated"))
        .unwrap_or_else(|| "Web app catalog update".to_string());
    update_row(
        title,
        subtitle,
        json_string(update, "icon_path").unwrap_or_default(),
        json_string(update, "icon_url").unwrap_or_default(),
        action,
        busy,
    )
}

fn appimage_row(update: &serde_json::Value, action: Message, busy: bool) -> Element<'static, Message> {
    let title = json_string(update, "name")
        .or_else(|| json_string(update, "id"))
        .unwrap_or_else(|| "AppImage update".to_string());
    let subtitle = format!(
        "{} -> {}",
        json_string(update, "current_version").unwrap_or_else(|| "installed".to_string()),
        json_string(update, "new_version").unwrap_or_else(|| "available".to_string())
    );
    update_row(
        title,
        subtitle,
        json_string(update, "icon_path").unwrap_or_default(),
        String::new(),
        action,
        busy,
    )
}

fn update_row(
    title: String,
    subtitle: String,
    icon_path: String,
    icon_url: String,
    action: Message,
    busy: bool,
) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let button: Element<_> = if busy {
        widget::button::standard("Updating...").into()
    } else {
        widget::button::suggested("Update")
            .on_press(action)
            .into()
    };

    widget::container(
        widget::row::with_children(vec![
            common::app_icon(&icon_path, &icon_url, 36),
            widget::column::with_children(vec![
                widget::text::body(title).into(),
                widget::text::caption(subtitle).into(),
            ])
            .spacing(2)
            .width(Length::Fill)
            .into(),
            button,
        ])
        .spacing(spacing.space_s)
        .align_y(Alignment::Center),
    )
    .width(Length::Fill)
    .padding([spacing.space_xxs, spacing.space_s])
    .class(theme::Container::Card)
    .into()
}
