use crossterm::event::{KeyCode, KeyEvent};
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Clear, Paragraph, Wrap};
use ratatui::Frame;

use crate::tui::app::{AddField, AddTarget, App, ConfirmAction, EditField, Modal};
use crate::tui::event::Loadable;
use crate::tui::{fetch, theme};

impl App {
    pub(crate) fn handle_modal_key(&mut self, key: KeyEvent) {
        match self.modal.take() {
            None => {}
            Some(Modal::Help) => {
                // Any key closes help.
            }
            Some(Modal::Confirm { msg, toggle_label, mut toggle, action }) => match key.code {
                KeyCode::Enter | KeyCode::Char('y') => self.execute_confirm(action, toggle),
                KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('q') => {}
                KeyCode::Char('f') if toggle_label.is_some() => {
                    toggle = !toggle;
                    self.modal = Some(Modal::Confirm { msg, toggle_label, toggle, action });
                }
                _ => self.modal = Some(Modal::Confirm { msg, toggle_label, toggle, action }),
            },
            Some(Modal::Add(mut add)) => match key.code {
                KeyCode::Esc => {}
                KeyCode::Enter => self.execute_add(add),
                KeyCode::Tab | KeyCode::Char('j') | KeyCode::Down => {
                    add.field = add_field_next(add.field);
                    self.modal = Some(Modal::Add(add));
                }
                KeyCode::BackTab | KeyCode::Char('k') | KeyCode::Up => {
                    add.field = add_field_prev(add.field);
                    self.modal = Some(Modal::Add(add));
                }
                KeyCode::Char('h') | KeyCode::Left => {
                    add_cycle(&mut add, -1);
                    self.modal = Some(Modal::Add(add));
                }
                KeyCode::Char('l') | KeyCode::Right | KeyCode::Char(' ') => {
                    add_cycle(&mut add, 1);
                    self.modal = Some(Modal::Add(add));
                }
                _ => self.modal = Some(Modal::Add(add)),
            },
            Some(Modal::Edit(mut edit)) => match key.code {
                KeyCode::Esc => {}
                KeyCode::Enter => self.execute_edit(edit),
                KeyCode::Tab | KeyCode::Char('j') | KeyCode::Down => {
                    edit.field = edit_field_next(edit.field);
                    self.modal = Some(Modal::Edit(edit));
                }
                KeyCode::BackTab | KeyCode::Char('k') | KeyCode::Up => {
                    edit.field = edit_field_prev(edit.field);
                    self.modal = Some(Modal::Edit(edit));
                }
                KeyCode::Char('h') | KeyCode::Left => {
                    edit_cycle(&mut edit, -1);
                    self.modal = Some(Modal::Edit(edit));
                }
                KeyCode::Char('l') | KeyCode::Right | KeyCode::Char(' ') => {
                    edit_cycle(&mut edit, 1);
                    self.modal = Some(Modal::Edit(edit));
                }
                _ => self.modal = Some(Modal::Edit(edit)),
            },
        }
    }

    fn execute_confirm(&mut self, action: ConfirmAction, toggle: bool) {
        let tx = self.tx.clone();
        let origin = self.tab;
        match action {
            ConfirmAction::DeleteMovie { id, title } => {
                let Some(radarr) = self.clients.radarr.clone() else { return };
                fetch::action(tx, origin, format!("removed {title}"), async move {
                    radarr.delete_movie(id, toggle, false).await
                });
            }
            ConfirmAction::DeleteSeries { id, title } => {
                let Some(sonarr) = self.clients.sonarr.clone() else { return };
                fetch::action(tx, origin, format!("removed {title}"), async move {
                    sonarr.delete_series(id, toggle, false).await
                });
            }
            ConfirmAction::RemoveQueueItem { radarr, id, blocklist } => {
                let desc = if blocklist {
                    format!("blocklisted queue item {id}")
                } else {
                    format!("removed queue item {id}")
                };
                if radarr {
                    let Some(client) = self.clients.radarr.clone() else { return };
                    fetch::action(tx, origin, desc, async move {
                        client.queue_delete(id, blocklist, toggle).await
                    });
                } else {
                    let Some(client) = self.clients.sonarr.clone() else { return };
                    fetch::action(tx, origin, desc, async move {
                        client.queue_delete(id, blocklist, toggle).await
                    });
                }
            }
            ConfirmAction::DeleteTorrent { hash, name } => {
                let Some(qbit) = self.clients.qbit.clone() else { return };
                fetch::action(tx, origin, format!("deleted {name}"), async move {
                    qbit.delete(&[hash], toggle).await
                });
            }
            ConfirmAction::DeleteNzb { id, name } => {
                let Some(nzbget) = self.clients.nzbget.clone() else { return };
                fetch::action(tx, origin, format!("deleted {name}"), async move {
                    nzbget.delete(&[id]).await.map(|_| ())
                });
            }
        }
    }

    fn execute_add(&mut self, add: crate::tui::app::AddModal) {
        // Every early return must re-install the modal, or the user's
        // choices silently vanish under a toast.
        let picked = match &add.options {
            Loadable::Ready((profiles, roots)) => {
                match (profiles.get(add.profile_idx), roots.get(add.root_idx)) {
                    (Some(p), Some(r)) => Some((p.id, r.path.clone())),
                    (None, _) => {
                        self.toast_err("the service has no quality profiles");
                        None
                    }
                    (_, None) => {
                        self.toast_err("the service has no root folders; add one in its settings");
                        None
                    }
                }
            }
            // Options still loading (or failed): keep the modal up.
            _ => None,
        };
        let Some((profile_id, root_path)) = picked else {
            self.modal = Some(Modal::Add(add));
            return;
        };
        let tx = self.tx.clone();
        let origin = self.tab;
        let (monitored, search) = (add.monitored, add.search_on_add);

        match add.target {
            AddTarget::Movie(movie) => {
                let Some(radarr) = self.clients.radarr.clone() else {
                    self.toast_err("radarr is not configured");
                    return;
                };
                let title = movie.title.clone();
                fetch::action(tx, origin, format!("added {title}"), async move {
                    radarr
                        .add_movie(&movie, profile_id, &root_path, monitored, search)
                        .await
                        .map(|_| ())
                });
            }
            AddTarget::Series(series) => {
                let Some(sonarr) = self.clients.sonarr.clone() else {
                    self.toast_err("sonarr is not configured");
                    return;
                };
                let title = series.title.clone();
                fetch::action(tx, origin, format!("added {title}"), async move {
                    sonarr
                        .add_series(&series, profile_id, &root_path, monitored, true, search)
                        .await
                        .map(|_| ())
                });
            }
        }
    }

    fn execute_edit(&mut self, edit: crate::tui::app::EditModal) {
        let profile_id = match &edit.options {
            Loadable::Ready((profiles, _)) => match profiles.get(edit.profile_idx) {
                Some(p) => Some(p.id),
                None => {
                    self.toast_err("the service has no quality profiles");
                    None
                }
            },
            _ => None,
        };
        let Some(profile_id) = profile_id else {
            self.modal = Some(Modal::Edit(edit));
            return;
        };
        let (id, monitored) = (edit.id, edit.monitored);
        let tx = self.tx.clone();
        let origin = self.tab;

        if edit.is_movie {
            let Some(radarr) = self.clients.radarr.clone() else { return };
            fetch::action(tx, origin, format!("updated {}", edit.title), async move {
                radarr.edit_movie(id, profile_id, monitored).await
            });
        } else {
            let Some(sonarr) = self.clients.sonarr.clone() else { return };
            fetch::action(tx, origin, format!("updated {}", edit.title), async move {
                sonarr.edit_series(id, profile_id, monitored).await
            });
        }
    }

    pub(crate) fn draw_modal(&mut self, f: &mut Frame, area: Rect) {
        let Some(modal) = &self.modal else { return };
        match modal {
            Modal::Help => draw_help(f, area),
            Modal::Confirm { msg, toggle_label, toggle, .. } => {
                let rect = centered_rect(area, 60, 9);
                f.render_widget(Clear, rect);
                f.render_widget(Block::new().style(Style::new().bg(theme::SURFACE)), rect);
                let block = theme::panel("Confirm", theme::WARN, true);
                let inner = block.inner(rect);
                f.render_widget(block, rect);
                let mut lines = vec![Line::raw(msg.clone()), Line::raw("")];
                if let Some(label) = toggle_label {
                    lines.push(Line::from(vec![
                        Span::styled("f ", theme::accent_bold(theme::ACCENT)),
                        Span::raw(format!("{label}: ")),
                        Span::styled(
                            if *toggle { "YES" } else { "no" },
                            if *toggle {
                                theme::accent_bold(theme::ERROR)
                            } else {
                                theme::dim()
                            },
                        ),
                    ]));
                    lines.push(Line::raw(""));
                }
                lines.push(Line::from(vec![
                    Span::styled("Enter ", theme::accent_bold(theme::SUCCESS)),
                    Span::styled("confirm   ", theme::dim()),
                    Span::styled("Esc ", theme::accent_bold(theme::ERROR)),
                    Span::styled("cancel", theme::dim()),
                ]));
                f.render_widget(Paragraph::new(lines).wrap(Wrap { trim: true }), inner);
            }
            Modal::Add(add) => {
                let rect = centered_rect(area, 62, 13);
                f.render_widget(Clear, rect);
                f.render_widget(Block::new().style(Style::new().bg(theme::SURFACE)), rect);
                let title = format!("Add · {}", add.title());
                let block = theme::panel(&title, theme::SUCCESS, true);
                let inner = block.inner(rect);
                f.render_widget(block, rect);

                let mut lines: Vec<Line> = Vec::new();
                match &add.options {
                    Loadable::Ready((profiles, roots)) => {
                        let profile = profiles
                            .get(add.profile_idx)
                            .map(|p| p.name.clone())
                            .unwrap_or_default();
                        let root = roots.get(add.root_idx).map(|r| r.path.clone()).unwrap_or_default();
                        lines.push(field_line(add.field == AddField::Profile, "Quality profile", &format!("◂ {profile} ▸")));
                        lines.push(field_line(add.field == AddField::Root, "Root folder", &format!("◂ {root} ▸")));
                        lines.push(field_line(
                            add.field == AddField::Monitored,
                            "Monitored",
                            if add.monitored { "yes" } else { "no" },
                        ));
                        lines.push(field_line(
                            add.field == AddField::SearchOnAdd,
                            "Search on add",
                            if add.search_on_add { "yes" } else { "no" },
                        ));
                        lines.push(Line::raw(""));
                        let confirm_style = if add.field == AddField::Confirm {
                            theme::accent_bold(theme::SUCCESS)
                        } else {
                            theme::dim()
                        };
                        lines.push(Line::styled("[ Add ]  (Enter)", confirm_style));
                    }
                    Loadable::Loading => lines.push(Line::raw("loading profiles and folders…")),
                    Loadable::Failed(e) => {
                        lines.push(Line::styled(e.clone(), theme::accent_bold(theme::ERROR)))
                    }
                    Loadable::NotAsked => {}
                }
                lines.push(Line::raw(""));
                lines.push(Line::styled(
                    "j/k fields · h/l change · Enter add · Esc cancel",
                    theme::dim(),
                ));
                f.render_widget(Paragraph::new(lines), inner);
            }
            Modal::Edit(edit) => {
                let rect = centered_rect(area, 62, 11);
                f.render_widget(Clear, rect);
                f.render_widget(Block::new().style(Style::new().bg(theme::SURFACE)), rect);
                let title = format!("Edit · {}", edit.title);
                let block = theme::panel(&title, theme::ACCENT, true);
                let inner = block.inner(rect);
                f.render_widget(block, rect);

                let mut lines: Vec<Line> = Vec::new();
                match &edit.options {
                    Loadable::Ready((profiles, _)) => {
                        let profile = profiles
                            .get(edit.profile_idx)
                            .map(|p| p.name.clone())
                            .unwrap_or_default();
                        lines.push(field_line(edit.field == EditField::Profile, "Quality profile", &format!("◂ {profile} ▸")));
                        lines.push(field_line(
                            edit.field == EditField::Monitored,
                            "Monitored",
                            if edit.monitored { "yes" } else { "no" },
                        ));
                        lines.push(Line::raw(""));
                        let confirm_style = if edit.field == EditField::Confirm {
                            theme::accent_bold(theme::SUCCESS)
                        } else {
                            theme::dim()
                        };
                        lines.push(Line::styled("[ Save ]  (Enter)", confirm_style));
                    }
                    Loadable::Loading => lines.push(Line::raw("loading profiles…")),
                    Loadable::Failed(e) => {
                        lines.push(Line::styled(e.clone(), theme::accent_bold(theme::ERROR)))
                    }
                    Loadable::NotAsked => {}
                }
                lines.push(Line::raw(""));
                lines.push(Line::styled(
                    "j/k fields · h/l change · Enter save · Esc cancel",
                    theme::dim(),
                ));
                f.render_widget(Paragraph::new(lines), inner);
            }
        }
    }
}

fn field_line<'a>(focused: bool, label: &'a str, value: &str) -> Line<'a> {
    let marker = if focused { theme::SELECT_MARKER } else { " " };
    let style = if focused {
        theme::accent_bold(theme::FG)
    } else {
        theme::dim()
    };
    Line::from(vec![
        Span::styled(marker, theme::accent_bold(theme::ACCENT)),
        Span::styled(format!(" {label:<16}"), style),
        Span::styled(value.to_string(), style),
    ])
}

fn add_field_next(f: AddField) -> AddField {
    match f {
        AddField::Profile => AddField::Root,
        AddField::Root => AddField::Monitored,
        AddField::Monitored => AddField::SearchOnAdd,
        AddField::SearchOnAdd => AddField::Confirm,
        AddField::Confirm => AddField::Profile,
    }
}

fn add_field_prev(f: AddField) -> AddField {
    match f {
        AddField::Profile => AddField::Confirm,
        AddField::Root => AddField::Profile,
        AddField::Monitored => AddField::Root,
        AddField::SearchOnAdd => AddField::Monitored,
        AddField::Confirm => AddField::SearchOnAdd,
    }
}

fn add_cycle(add: &mut crate::tui::app::AddModal, delta: i64) {
    let Loadable::Ready((profiles, roots)) = &add.options else { return };
    match add.field {
        AddField::Profile => {
            if !profiles.is_empty() {
                let len = profiles.len() as i64;
                add.profile_idx = ((add.profile_idx as i64 + delta).rem_euclid(len)) as usize;
            }
        }
        AddField::Root => {
            if !roots.is_empty() {
                let len = roots.len() as i64;
                add.root_idx = ((add.root_idx as i64 + delta).rem_euclid(len)) as usize;
            }
        }
        AddField::Monitored => add.monitored = !add.monitored,
        AddField::SearchOnAdd => add.search_on_add = !add.search_on_add,
        AddField::Confirm => {}
    }
}

fn edit_field_next(f: EditField) -> EditField {
    match f {
        EditField::Profile => EditField::Monitored,
        EditField::Monitored => EditField::Confirm,
        EditField::Confirm => EditField::Profile,
    }
}

fn edit_field_prev(f: EditField) -> EditField {
    match f {
        EditField::Profile => EditField::Confirm,
        EditField::Monitored => EditField::Profile,
        EditField::Confirm => EditField::Monitored,
    }
}

fn edit_cycle(edit: &mut crate::tui::app::EditModal, delta: i64) {
    let Loadable::Ready((profiles, _)) = &edit.options else { return };
    match edit.field {
        EditField::Profile => {
            if !profiles.is_empty() {
                let len = profiles.len() as i64;
                edit.profile_idx = ((edit.profile_idx as i64 + delta).rem_euclid(len)) as usize;
            }
        }
        EditField::Monitored => edit.monitored = !edit.monitored,
        EditField::Confirm => {}
    }
}

fn draw_help(f: &mut Frame, area: Rect) {
    let rect = centered_rect(area, 64, 20);
    f.render_widget(Clear, rect);
    f.render_widget(Block::new().style(Style::new().bg(theme::SURFACE)), rect);
    let block = theme::panel("Help", theme::ACCENT, true);
    let inner = block.inner(rect);
    f.render_widget(block, rect);

    let rows: [(&str, &str); 16] = [
        ("1-6 / Tab", "switch tab"),
        ("j/k or ↑/↓", "move selection"),
        ("r", "refresh current tab"),
        ("q / Ctrl-C", "quit"),
        ("", ""),
        ("Search:  type", "results appear live (movies + series)"),
        ("↑/↓ · Enter", "select a result · add it to the library"),
        ("Esc", "stop typing (j/k, tabs work again)"),
        ("", ""),
        ("Library:  ←/→", "movies/series · / filter · s search release · e edit · d delete"),
        ("", ""),
        ("Downloads:  p / P", "pause / resume (qbit + nzbget)"),
        ("d", "delete download"),
        ("b", "blocklist arr queue item"),
        ("", ""),
        ("Missing:  s / a", "search selected / search everything"),
    ];
    let lines: Vec<Line> = rows
        .iter()
        .map(|(key, desc)| {
            Line::from(vec![
                Span::styled(format!("{key:<14}"), theme::accent_bold(theme::ACCENT)),
                Span::styled(desc.to_string(), Style::new().fg(theme::FG)),
            ])
        })
        .collect();
    f.render_widget(Paragraph::new(lines), inner);
}

fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
    let width = width.min(area.width.saturating_sub(2));
    let height = height.min(area.height.saturating_sub(2));
    let [_, mid, _] = Layout::vertical([
        Constraint::Min(0),
        Constraint::Length(height),
        Constraint::Min(0),
    ])
    .areas(area);
    let [_, rect, _] = Layout::horizontal([
        Constraint::Min(0),
        Constraint::Length(width),
        Constraint::Min(0),
    ])
    .areas(mid);
    rect
}
