use crate::{
    DESKTOP_IMAGES, IMAGE_BRANCHES, Message, SoftwareApp, SystemDialog,
};
use cosmic::iced::{Alignment, Length};
use cosmic::{theme, widget, Element};

pub fn view(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let mut rows: Vec<Element<_>> = vec![
        widget::text::title2("System").into(),
        widget::text::body("Manage the booted image, desktop image channel, and RakuOS overlay state.").into(),
    ];

    if app.system_loading {
        rows.push(
            widget::row::with_children(vec![
                widget::text::body("Loading system info...").into(),
            ])
            .spacing(spacing.space_s)
            .into(),
        );
    }

    if let Some(data) = &app.system_data {
        rows.push(booted_image_card(app));
        rows.push(desktop_environment_card(app));
        rows.push(overlay_packages_card(app));

        if let Some(error) = &data.status.error {
            rows.push(status_card(false, format!("System status error: {error}")));
        }
    } else if !app.system_loading {
        rows.push(status_card(false, "System status has not loaded yet.".to_string()));
    }

    if !app.system_status.is_empty() {
        rows.push(status_card(app.system_success, app.system_status.clone()));
    }

    widget::column::with_children(rows)
        .padding([0, spacing.space_s, spacing.space_m, spacing.space_s])
        .spacing(spacing.space_s)
        .width(Length::Fill)
        .into()
}

pub(crate) fn popup(app: &SoftwareApp) -> Element<'static, Message> {
    match app.system_dialog {
        Some(SystemDialog::Rollback) => rollback_dialog(),
        Some(SystemDialog::SwitchConfirm) => switch_confirm_dialog(app),
        Some(SystemDialog::SwitchProgress) => switch_progress_dialog(app),
        Some(SystemDialog::OverlayReset) => overlay_reset_dialog(),
        Some(SystemDialog::KernelArgs) => kernel_args_dialog(app),
        None => widget::space::vertical().into(),
    }
}

fn booted_image_card(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let Some(data) = &app.system_data else {
        return widget::space::vertical().into();
    };
    let parsed = parse_image_ref(&data.status.image);
    let rows = vec![
        widget::row::with_children(vec![
            widget::text::title4("Booted Image").into(),
            widget::space::horizontal().width(Length::Fill).into(),
            widget::button::standard("Kernel Parameters…")
                .on_press(Message::OpenKernelArgsDialog)
                .into(),
            widget::button::destructive("Rollback")
                .on_press(Message::OpenSystemDialog(SystemDialog::Rollback))
                .into(),
            if app.system_reboot_required {
                widget::button::suggested("Reboot to Apply")
                    .on_press(Message::RebootSystem)
                    .into()
            } else {
                widget::space::horizontal().into()
            },
        ])
        .spacing(spacing.space_s)
        .align_y(Alignment::Center)
        .into(),
        widget::divider::horizontal::default().into(),
        kv("Image", dash(&data.status.image)),
        kv("Version", display_image_version(&data.status.image, &data.status.version)),
        kv("Digest", shorten(&data.status.digest)),
        kv("Timestamp", dash(&data.status.timestamp)),
        kv("Nvidia", if parsed.is_nvidia { "Yes".to_string() } else { "No".to_string() }),
    ];
    card(rows)
}

fn desktop_environment_card(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let Some(data) = &app.system_data else {
        return widget::space::vertical().into();
    };
    let parsed = parse_image_ref(&data.status.image);
    let current_de = current_de_label(&parsed.name);
    let current_branch = current_branch_label(&parsed.tag);
    let selected_de = DESKTOP_IMAGES.get(app.system_switch_de).unwrap_or(&DESKTOP_IMAGES[0]);
    let selected_branch = IMAGE_BRANCHES.get(app.system_switch_branch).unwrap_or(&IMAGE_BRANCHES[0]);
    let target = selected_switch_target(&data.status.image, app.system_switch_de, app.system_switch_branch);
    let differs = selected_de.image_name != parsed.name || selected_branch.label != current_branch;

    let mut rows: Vec<Element<_>> = vec![
        widget::text::title4("Desktop Environment").into(),
        widget::divider::horizontal::default().into(),
        kv("Current", format!("{current_de}{}", if parsed.is_nvidia { " (Nvidia)" } else { "" })),
        kv("Branch", current_branch.to_string()),
        widget::text::heading("Switch to:").into(),
        widget::row::with_children(
            DESKTOP_IMAGES
                .iter()
                .enumerate()
                .map(|(index, image)| select_button(image.label, index == app.system_switch_de, Message::SelectSystemDe(index)))
                .collect::<Vec<_>>(),
        )
        .spacing(spacing.space_xs)
        .into(),
        widget::row::with_children(
            IMAGE_BRANCHES
                .iter()
                .enumerate()
                .map(|(index, branch)| select_button(branch.label, index == app.system_switch_branch, Message::SelectSystemBranch(index)))
                .collect::<Vec<_>>(),
        )
        .spacing(spacing.space_xs)
        .into(),
    ];

    if differs {
        rows.push(
            widget::button::suggested("Switch Image")
                .on_press(Message::PrepareSwitchImage(
                    format!("{} on {}", target.de_label, target.branch_label),
                    target.repo_url,
                    target.tag,
                ))
                .into(),
        );
    } else {
        rows.push(widget::text::caption("Current image already matches this selection.").into());
    }

    card(rows)
}

fn overlay_packages_card(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let Some(data) = &app.system_data else {
        return widget::space::vertical().into();
    };
    let mut rows: Vec<Element<_>> = vec![
        widget::row::with_children(vec![
            widget::text::title4("Overlay Packages").into(),
            widget::space::horizontal().width(Length::Fill).into(),
            widget::text::caption(format!(
                "{} package{}",
                data.overlay.package_count,
                if data.overlay.package_count == 1 { "" } else { "s" }
            ))
            .into(),
            if app.system_reboot_required {
                widget::button::suggested("Reboot to Apply")
                    .on_press(Message::RebootSystem)
                    .into()
            } else {
                widget::button::destructive("Reset Overlay")
                    .on_press(Message::OpenSystemDialog(SystemDialog::OverlayReset))
                    .into()
            },
        ])
        .spacing(spacing.space_s)
        .align_y(Alignment::Center)
        .into(),
        widget::divider::horizontal::default().into(),
    ];

    if data.overlay.packages.is_empty() {
        rows.push(widget::text::caption("No overlay packages installed.").into());
    } else {
        for package in &data.overlay.packages {
            rows.push(widget::text::body(format!("- {package}")).into());
        }
    }

    card(rows)
}

fn rollback_dialog() -> Element<'static, Message> {
    dialog(
        "Roll Back System?",
        vec![
            widget::text::body("This will roll back to the previous OS image and perform a soft overlay reset.\n\nYour installed packages list is preserved. The overlay will be rebuilt from it on next boot.\n\npkexec will prompt for your password twice.").into(),
            warning_card(
                "Roll Back + Soft Reset",
                "Reverts the OS image and schedules an overlay rebuild. Current overlay changes will be wiped and reinstalled from your packages list on next boot.",
                false,
                Message::SystemAction("rollback".to_string()),
                "Roll Back & Reset Overlay",
            ),
            widget::button::standard("Cancel").on_press(Message::CloseSystemDialog).width(Length::Fill).into(),
        ],
    )
}

fn switch_confirm_dialog(app: &SoftwareApp) -> Element<'static, Message> {
    let rawhide = app.system_switch_tag == "rawhide";
    let mut text = format!(
        "Switching images performs a full overlay reset before the new image is staged.\n\nThat means your current overlay changes and saved packages list will be wiped, and then the image switch will run with pkexec.\n\nDo you want to proceed with switching to {}?",
        app.system_switch_label
    );
    if rawhide {
        text.push_str("\n\nWARNING: Rawhide is an experimental rolling development branch. It can break unexpectedly, may fail to boot, and is not recommended for normal use.");
    }
    dialog(
        "Switch Image?",
        vec![
            widget::text::body(text).into(),
            warning_card(
                "Full Overlay Reset Required",
                if rawhide {
                    "The overlay will be fully reset first, then Rawhide will be staged. Rawhide is for testing only and should be expected to be unstable."
                } else {
                    "The overlay will be fully reset first, then the selected image will be staged. Keep this window open until the process completes."
                },
                false,
                Message::SwitchImage,
                "Proceed",
            ),
            widget::button::standard("Cancel").on_press(Message::CloseSystemDialog).width(Length::Fill).into(),
        ],
    )
}

fn switch_progress_dialog(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let status = if app.system_switch_running {
        format!("Please wait while RakuOS fully resets the overlay and switches to {}.", app.system_switch_label)
    } else if app.system_switch_success {
        "The image switch has been staged. Reboot to apply the new image.".to_string()
    } else {
        "The image switch failed. You can close this dialog and review the logs.".to_string()
    };

    let mut rows: Vec<Element<_>> = vec![
        widget::text::title3("Switching Image").into(),
        widget::text::body(status).into(),
    ];

    if app.system_switch_running {
        rows.push(widget::text::caption("Please wait...").into());
    }

    let log = if app.system_switch_log.is_empty() {
        "Waiting for output...".to_string()
    } else {
        app.system_switch_log.clone()
    };
    rows.push(
        widget::container(widget::text::body(log))
        .padding(spacing.space_s)
        .class(theme::Container::Card)
        .height(Length::Fixed(150.0))
        .width(Length::Fill)
        .into(),
    );

    if !app.system_switch_running {
        let mut actions: Vec<Element<_>> = vec![
            widget::button::standard("Close")
                .on_press(Message::CloseSystemDialog)
                .into(),
        ];
        if app.system_switch_success {
            actions.push(widget::button::suggested("Reboot to Apply").on_press(Message::RebootSystem).into());
        }
        rows.push(widget::row::with_children(actions).spacing(spacing.space_s).into());
    }

    dialog("Switching Image", rows)
}

fn overlay_reset_dialog() -> Element<'static, Message> {
    dialog(
        "Reset Overlay",
        vec![
            widget::text::body("Choose how to reset the overlay. Both options require a reboot to take effect. pkexec will prompt for your password.").into(),
            warning_card(
                "Soft Reset (Rebuild)",
                "Wipes the overlay and reinstalls all packages from your packages list on next boot. Your installed package list is preserved.\n\nA network connection is required during the next boot to complete the rebuild.",
                true,
                Message::SystemAction("soft-reset".to_string()),
                "Soft Reset",
            ),
            warning_card(
                "Full Reset (Stock)",
                "Completely wipes the overlay and your packages list. The system returns to the base image state. All installed packages will be removed.",
                false,
                Message::SystemAction("full-reset".to_string()),
                "Full Reset",
            ),
            widget::button::standard("Cancel").on_press(Message::CloseSystemDialog).width(Length::Fill).into(),
        ],
    )
}

fn kernel_args_dialog(app: &SoftwareApp) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let cmdline  = if app.kargs_cmdline.is_empty() { "-".to_string() } else { app.kargs_cmdline.clone() };
    let input    = app.kargs_input.clone();

    let mut rows: Vec<Element<_>> = vec![
        widget::text::heading("Current kernel command line:").into(),
        widget::container(
            widget::scrollable(
                widget::text::body(cmdline).width(Length::Fill),
            )
            .height(Length::Fixed(90.0)),
        )
        .padding(spacing.space_xs)
        .class(theme::Container::Card)
        .width(Length::Fill)
        .into(),
        widget::text::heading("User-managed parameters (rakuos-user source):").into(),
        widget::text::caption("Space-separated kernel arguments. Leave empty to remove all user-set args.").into(),
        widget::text_input::text_input("e.g. mitigations=off quiet splash", input)
            .on_input(Message::KernelArgsInputChanged)
            .width(Length::Fill)
            .into(),
    ];

    if !app.kargs_status.is_empty() {
        rows.push(status_card(app.kargs_success, app.kargs_status.clone()));
    }

    let mut actions: Vec<Element<_>> = vec![];
    if !app.kargs_running {
        actions.push(
            widget::button::standard("Cancel")
                .on_press(Message::CloseSystemDialog)
                .into(),
        );
        actions.push(
            widget::button::destructive("Clear All")
                .on_press(Message::KernelArgsClear)
                .into(),
        );
        actions.push(
            widget::button::suggested("Apply")
                .on_press(Message::ApplyKernelArgs)
                .into(),
        );
        if app.kargs_success {
            actions.push(
                widget::button::suggested("Reboot to Apply")
                    .on_press(Message::RebootSystem)
                    .into(),
            );
        }
    } else {
        actions.push(widget::text::caption("Applying…").into());
    }
    rows.push(widget::row::with_children(actions).spacing(spacing.space_s).into());

    dialog("Kernel Parameters", rows)
}

fn card(children: Vec<Element<'static, Message>>) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    widget::container(
        widget::column::with_children(children)
            .spacing(spacing.space_xs)
            .width(Length::Fill),
    )
    .padding(spacing.space_s)
    .class(theme::Container::Card)
    .width(Length::Fill)
    .into()
}

fn status_card(ok: bool, text: String) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let label = if ok { format!("OK: {text}") } else { text };
    widget::container(widget::text::body(label))
        .padding(spacing.space_s)
        .class(theme::Container::Card)
        .width(Length::Fill)
        .into()
}

fn dialog(title: &'static str, children: Vec<Element<'static, Message>>) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let mut rows = vec![widget::text::title3(title).into()];
    rows.extend(children);
    widget::container(
        widget::column::with_children(rows)
            .spacing(spacing.space_s)
            .width(Length::Fill),
    )
    .padding(spacing.space_m)
    .class(theme::Container::Dialog)
    .width(Length::Fixed(520.0))
    .into()
}

fn warning_card(
    title: &'static str,
    body: &'static str,
    safe: bool,
    message: Message,
    button: &'static str,
) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    let action: Element<_> = if safe {
        widget::button::suggested(button).on_press(message).width(Length::Fill).into()
    } else {
        widget::button::destructive(button).on_press(message).width(Length::Fill).into()
    };
    widget::container(
        widget::column::with_children(vec![
            widget::text::heading(title).into(),
            widget::text::body(body).into(),
            action,
        ])
        .spacing(spacing.space_xs),
    )
    .padding(spacing.space_s)
    .class(theme::Container::Card)
    .width(Length::Fill)
    .into()
}

fn kv(label: &'static str, value: String) -> Element<'static, Message> {
    let spacing = theme::active().cosmic().spacing;
    widget::row::with_children(vec![
        widget::text::caption(label).width(Length::Fixed(90.0)).into(),
        widget::text::body(value).width(Length::Fill).into(),
    ])
    .spacing(spacing.space_s)
    .align_y(Alignment::Center)
    .into()
}

fn select_button(label: &'static str, active: bool, message: Message) -> Element<'static, Message> {
    if active {
        widget::button::suggested(label).on_press(message).into()
    } else {
        widget::button::standard(label).on_press(message).into()
    }
}

struct ParsedImage {
    name: String,
    tag: String,
    is_nvidia: bool,
}

struct SwitchTarget {
    repo_url: String,
    tag: String,
    de_label: &'static str,
    branch_label: &'static str,
}

fn parse_image_ref(image_ref: &str) -> ParsedImage {
    let (image_path, tag) = image_ref.rsplit_once(':').unwrap_or((image_ref, ""));
    let path_without_registry = image_path
        .find('/')
        .map(|index| &image_path[index + 1..])
        .unwrap_or(image_path);
    let normalized = path_without_registry
        .split_once("rakuos/")
        .map(|(_, rest)| rest)
        .unwrap_or(path_without_registry);
    let is_nvidia = normalized.ends_with("/nvidia") || normalized.ends_with("-nvidia");
    let name = if let Some(without_nvidia) = normalized.strip_suffix("/nvidia") {
        without_nvidia.rsplit('/').next().unwrap_or("").to_string()
    } else {
        normalized
            .rsplit('/')
            .next()
            .unwrap_or("")
            .trim_end_matches("-nvidia")
            .to_string()
    };
    ParsedImage { name, tag: tag.to_string(), is_nvidia }
}

fn selected_switch_target(current_image: &str, de_index: usize, branch_index: usize) -> SwitchTarget {
    let selected = DESKTOP_IMAGES.get(de_index).unwrap_or(&DESKTOP_IMAGES[0]);
    let branch = IMAGE_BRANCHES.get(branch_index).unwrap_or(&IMAGE_BRANCHES[0]);
    let parsed = parse_image_ref(current_image);
    SwitchTarget {
        repo_url: canonical_repo_url(selected.image_name, parsed.is_nvidia),
        tag: branch.tag.to_string(),
        de_label: selected.label,
        branch_label: branch.label,
    }
}

fn canonical_repo_url(de_id: &str, is_nvidia: bool) -> String {
    if is_nvidia {
        format!("registry.gitlab.com/rakuos/images/{de_id}/nvidia")
    } else {
        format!("registry.gitlab.com/rakuos/images/{de_id}")
    }
}

fn current_de_label(image_name: &str) -> String {
    DESKTOP_IMAGES
        .iter()
        .find(|image| image.image_name == image_name)
        .map(|image| image.label.to_string())
        .unwrap_or_else(|| dash(image_name))
}

fn current_branch_label(tag: &str) -> &'static str {
    if tag.starts_with("staging") {
        "Staging"
    } else if tag.starts_with("rawhide") {
        "Rawhide"
    } else {
        "Stable"
    }
}

fn display_image_version(image_ref: &str, version: &str) -> String {
    let parsed = parse_image_ref(image_ref);
    let tag = parsed.tag;
    let date = version
        .as_bytes()
        .windows(8)
        .find_map(|bytes| {
            let text = std::str::from_utf8(bytes).ok()?;
            text.chars().all(|c| c.is_ascii_digit()).then(|| text.to_string())
        })
        .unwrap_or_default();

    if tag.len() == 8 && tag.chars().all(|c| c.is_ascii_digit()) {
        tag
    } else if ["latest", "staging", "rawhide"].contains(&tag.as_str()) {
        if date.is_empty() { tag } else { format!("{tag}.{date}") }
    } else if !tag.is_empty() && !date.is_empty() {
        format!("{tag}.{date}")
    } else {
        dash(version)
    }
}

fn shorten(value: &str) -> String {
    if value.is_empty() {
        "-".to_string()
    } else if value.len() > 24 {
        format!("{}...", &value[..24])
    } else {
        value.to_string()
    }
}

fn dash(value: &str) -> String {
    if value.is_empty() {
        "-".to_string()
    } else {
        value.to_string()
    }
}
