use crossterm::event::KeyCode;
use ratatui::{
    Frame,
    layout::{Constraint, Layout, Margin},
    style::{Color, Style},
    text::{Line, Span},
    widgets::{Cell, Row, Table, TableState},
};
use zeroize::Zeroizing;

use crate::{
    clipboard,
    screen::{
        Screen,
        confirm::{Confirm, ConfirmKind},
        export::{ExportState, ExportTotp},
        vault_list::VaultList,
    },
    totp::{TotpEntry, totp_ttl},
    ui::{ACCENT, DIM, GREEN, SUBTEXT, TEXT, full_separator, key_hint, render_version, ttl_color},
    vault::Vault,
};

#[derive(Debug, Default)]
pub struct AccountList {
    vault_name: String,
    password: Zeroizing<String>,
    entries: Vec<TotpEntry>,
    state: TableState,
    notification: Option<String>,
    popup: Option<PopUpKind>,
}

#[derive(Debug)]
enum PopUpKind {
    ExportTotp(ExportTotp),
    ConfirmDelete { confirm: Confirm, index: usize },
}

impl AccountList {
    pub fn new(vault_name: impl Into<String>, entries: Vec<TotpEntry>) -> Self {
        let mut state = TableState::default();
        state.select(Some(0));
        Self {
            vault_name: vault_name.into(),
            entries,
            state,
            notification: None,
            ..Default::default()
        }
    }

    pub fn with_password(mut self, password: Zeroizing<String>) -> Self {
        self.password = password;
        self
    }

    pub fn render(&mut self, frame: &mut Frame) {
        let area = frame.area();
        let inner = area.inner(Margin {
            horizontal: 2,
            vertical: 1,
        });

        let [title_area, sep_area, table_area, gauge_area, _, hint_area] = Layout::vertical([
            Constraint::Length(1), // vault name
            Constraint::Length(1), // separator
            Constraint::Fill(1),   // table
            Constraint::Length(1), // gauge
            Constraint::Length(1), // spacer
            Constraint::Length(1), // hints
        ])
        .areas(inner);

        let ttl = totp_ttl();
        let tc = ttl_color(ttl);

        frame.render_widget(
            Line::from(vec![
                Span::raw("  "),
                Span::styled(self.vault_name.clone(), Style::default().fg(TEXT).bold()),
            ]),
            title_area,
        );

        frame.render_widget(full_separator(area.width), sep_area);

        let col_constraints = [
            Constraint::Fill(1),   // name
            Constraint::Length(7), // code
            Constraint::Length(4), // ttl
        ];

        let header = Row::new(vec![
            Cell::from("  NAME").style(Style::default().fg(DIM).bold()),
            Cell::from("CODE").style(Style::default().fg(DIM).bold()),
            Cell::from("TTL").style(Style::default().fg(DIM).bold()),
        ]);

        let rows: Vec<Row> = if self.entries.is_empty() {
            vec![Row::new(vec![
                Cell::from("  no accounts").style(Style::default().fg(DIM)),
            ])]
        } else {
            self.entries
                .iter()
                .map(|e| {
                    let code = e.generate_otp().unwrap_or_else(|_| "error".into());
                    Row::new(vec![
                        Cell::from(format!("  {}", e.display_name()))
                            .style(Style::default().fg(TEXT)),
                        Cell::from(code).style(Style::default().fg(tc).bold()),
                        Cell::from(format!("{ttl}s")).style(Style::default().fg(DIM)),
                    ])
                })
                .collect()
        };

        let table = Table::new(rows, col_constraints)
            .header(header.bottom_margin(1))
            .column_spacing(2)
            .highlight_symbol("▸ ")
            .row_highlight_style(Style::default().fg(ACCENT).bold());

        frame.render_stateful_widget(table, table_area, &mut self.state);

        let filled = ((ttl as f64 / 30.0) * gauge_area.width as f64) as usize;
        let empty = gauge_area.width as usize - filled;
        let bar = Line::from(vec![
            Span::styled("█".repeat(filled), Style::default().fg(tc)),
            Span::styled(
                "░".repeat(empty),
                Style::default().fg(Color::Rgb(40, 40, 40)),
            ),
            Span::styled(format!("  {ttl}s"), Style::default().fg(DIM)),
        ]);
        frame.render_widget(bar, gauge_area);

        let [hint, version] =
            Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(hint_area);

        let hint_line = match &self.notification {
            Some(name) => Line::from(vec![
                Span::styled("✓ ", Style::default().fg(GREEN).bold()),
                Span::styled(name.clone(), Style::default().fg(TEXT)),
                Span::styled(" copied to clipboard", Style::default().fg(SUBTEXT)),
            ]),
            None => {
                let mut spans: Vec<Span> = vec![];
                spans.extend(key_hint("↵", "copy"));
                spans.extend(key_hint("↑↓ / jk", "navigate"));
                spans.extend(key_hint("e", "export"));
                spans.extend(key_hint("d", "delete"));
                spans.extend(key_hint("esc", "back"));
                spans.extend(key_hint("q", "quit"));
                Line::from(spans)
            }
        };
        frame.render_widget(hint_line, hint);
        render_version(frame, version);

        if let Some(kind) = &self.popup {
            match kind {
                PopUpKind::ExportTotp(popup) => popup.render(frame),
                PopUpKind::ConfirmDelete { confirm, .. } => confirm.render(frame),
            }
        }
    }

    pub fn handle_key(mut self, key: KeyCode) -> Screen {
        if let Some(kind) = self.popup.take() {
            match kind {
                PopUpKind::ExportTotp(popup) => {
                    return match popup.handle_key(key) {
                        ExportState::Active(export_totp) => {
                            self.popup = Some(PopUpKind::ExportTotp(export_totp));
                            Screen::AccountList(self)
                        }
                        ExportState::Closed => Screen::AccountList(self),
                    };
                }
                PopUpKind::ConfirmDelete { confirm, index } => {
                    return match confirm.handle_key(key) {
                        ConfirmKind::Yes => {
                            self.entries.remove(index);
                            match Vault::new(&self.vault_name)
                                .save(&self.entries, self.password.as_bytes())
                            {
                                Ok(_) => self.notification = Some("item deleted".into()),
                                Err(_) => self.notification = Some("failed to save".into()),
                            }

                            Screen::AccountList(self)
                        }
                        ConfirmKind::No | ConfirmKind::Stale => Screen::AccountList(self),
                    };
                }
            };
        }

        match key {
            KeyCode::Char('q') => Screen::Exit,
            KeyCode::Esc => Screen::VaultList(VaultList::new()),
            KeyCode::Down | KeyCode::Char('j') => {
                let i = self.state.selected().unwrap_or(0);
                self.state
                    .select(Some((i + 1).min(self.entries.len().saturating_sub(1))));
                Screen::AccountList(self)
            }
            KeyCode::Up | KeyCode::Char('k') => {
                let i = self.state.selected().unwrap_or(0);
                self.state.select(Some(i.saturating_sub(1)));
                Screen::AccountList(self)
            }
            KeyCode::Char('e') => {
                let entry = self.get_selected();
                match entry {
                    Some(entry) => {
                        self.popup = Some(PopUpKind::ExportTotp(ExportTotp::new(entry)));
                        Screen::AccountList(self)
                    }
                    None => Screen::AccountList(self),
                }
            }
            KeyCode::Char('d') => {
                if let Some(i) = self.state.selected()
                    && let Some(entry) = self.entries.get(i)
                {
                    self.popup = Some(PopUpKind::ConfirmDelete {
                        confirm: Confirm::new(format!("delete {}?", entry.display_name())),
                        index: i,
                    });
                }
                Screen::AccountList(self)
            }
            KeyCode::Enter => {
                if let Some(i) = self.state.selected()
                    && let Some(entry) = self.entries.get(i)
                    && let Ok(code) = entry.generate_otp()
                    && clipboard::copy_to_clipboard(&code)
                {
                    self.notification = Some(entry.display_name());
                }

                Screen::AccountList(self)
            }
            _ => Screen::AccountList(self),
        }
    }
    pub fn get_selected(&self) -> Option<TotpEntry> {
        self.entries.get(self.state.selected()?).cloned()
    }

    pub fn cleanup(&mut self) {
        self.notification = None;
        if let Some(kind) = &mut self.popup {
            match kind {
                PopUpKind::ExportTotp(popup) => popup.cleanup(),
                PopUpKind::ConfirmDelete { .. } => (),
            }
        }
    }
}
