use std::collections::VecDeque;

use crossterm::event::{KeyCode, KeyEvent};
use jsonwebtoken::decode_header;
use ratatui::{
    layout::{Alignment, Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span, Text},
    widgets::{Block, Borders, Paragraph, Wrap},
    Frame,
};
use serde_json::Value;
use sqlx::PgPool;

use crate::tui::app::AppAction;

const MAX_RECENT: usize = 10;

struct DecodedInfo {
    header: Value,
    payload: Value,
    sub: Option<String>,
    exp: Option<i64>,
    iat: Option<i64>,
}

enum VerifyStatus {
    Untrusted,
    Verified { token_type: String, age_human: String },
    Invalid(String),
}

struct UserRecord {
    id: String,
    email: Option<String>,
    active_tokens: usize,
}

pub struct AuthInspectorTab {
    pub token_input: String,
    decoded: Option<DecodedInfo>,
    verify_status: VerifyStatus,
    user: Option<UserRecord>,
    is_revoked: Option<bool>,
    error: Option<String>,
    recent_tokens: VecDeque<String>,
}

impl Default for AuthInspectorTab {
    fn default() -> Self {
        Self {
            token_input: String::new(),
            decoded: None,
            verify_status: VerifyStatus::Untrusted,
            user: None,
            is_revoked: None,
            error: None,
            recent_tokens: VecDeque::with_capacity(MAX_RECENT),
        }
    }
}

impl AuthInspectorTab {
    pub async fn load(&mut self, _pool: &PgPool) {}

    pub fn render(&mut self, frame: &mut Frame, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(3),
                Constraint::Min(0),
                Constraint::Length(1),
            ])
            .split(area);

        self.render_input(frame, chunks[0]);
        self.render_body(frame, chunks[1]);
        self.render_help(frame, chunks[2]);
    }

    fn render_input(&self, frame: &mut Frame, area: Rect) {
        let style = if self.error.is_some() {
            Style::default().fg(Color::Red)
        } else if self.token_input.is_empty() {
            Style::default().fg(Color::DarkGray)
        } else {
            Style::default().fg(Color::Yellow)
        };

        let input_text = if self.token_input.is_empty() {
            " Paste or type a JWT, then Enter to decode..."
        } else {
            &self.token_input
        };

        let input = Paragraph::new(input_text)
            .style(style)
            .block(Block::default().borders(Borders::ALL).title(" Token "));
        frame.render_widget(input, area);
    }

    fn render_body(&self, frame: &mut Frame, area: Rect) {
        let mut text = Text::default();

        if let Some(err) = &self.error {
            text.push_line(Line::from(Span::styled(
                format!(" Error: {err}"),
                Style::default().fg(Color::Red),
            )));
            text.push_line(Line::from(""));
        }

        if let Some(info) = &self.decoded {
            text.push_line(Line::from(Span::styled(
                " Header:",
                Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
            )));
            text.push_line(Line::from(Span::raw(format!(
                "   {}",
                serde_json::to_string_pretty(&info.header).unwrap_or_default()
            ))));

            text.push_line(Line::from(""));
            text.push_line(Line::from(Span::styled(
                " Payload:",
                Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
            )));
            text.push_line(Line::from(Span::raw(format!(
                "   {}",
                serde_json::to_string_pretty(&info.payload).unwrap_or_default()
            ))));

            let now = chrono::Utc::now().timestamp();
            if let Some(exp) = info.exp {
                let dt = chrono::DateTime::from_timestamp(exp, 0)
                    .map(|d| d.format("%Y-%m-%dT%H:%M:%S").to_string())
                    .unwrap_or_default();
                let delta = exp - now;
                let status = if delta < 0 {
                    format!(" (expired {} ago)", format_duration(-delta))
                } else {
                    format!(" (expires in {})", format_duration(delta))
                };
                let color = if delta < 0 { Color::Red } else { Color::Green };
                text.push_line(Line::from(""));
                text.push_line(Line::from(vec![
                    Span::styled(" Expiry: ", Style::default().fg(Color::Cyan)),
                    Span::styled(dt, Style::default().fg(color).add_modifier(Modifier::BOLD)),
                    Span::styled(status, Style::default().fg(color)),
                ]));
            }
            if let Some(iat) = info.iat {
                let dt = chrono::DateTime::from_timestamp(iat, 0)
                    .map(|d| d.format("%Y-%m-%dT%H:%M:%S").to_string())
                    .unwrap_or_default();
                text.push_line(Line::from(vec![
                    Span::styled(" Issued:  ", Style::default().fg(Color::Cyan)),
                    Span::raw(dt),
                ]));
            }

            text.push_line(Line::from(""));
            match &self.verify_status {
                VerifyStatus::Verified { token_type, age_human } => {
                    text.push_line(Line::from(vec![
                        Span::styled(" Signature: ", Style::default().fg(Color::Cyan)),
                        Span::styled(
                            "\u{2713} Valid",
                            Style::default()
                                .fg(Color::Green)
                                .add_modifier(Modifier::BOLD),
                        ),
                        Span::raw(format!(" (HS256, {token_type}, {age_human})")),
                    ]));
                }
                VerifyStatus::Invalid(reason) => {
                    text.push_line(Line::from(vec![
                        Span::styled(" Signature: ", Style::default().fg(Color::Cyan)),
                        Span::styled(
                            "\u{2717} Invalid",
                            Style::default()
                                .fg(Color::Red)
                                .add_modifier(Modifier::BOLD),
                        ),
                        Span::raw(format!(" \u{2014} {reason}")),
                    ]));
                }
                VerifyStatus::Untrusted => {
                    text.push_line(Line::from(vec![
                        Span::styled(" Signature: ", Style::default().fg(Color::Cyan)),
                        Span::styled(
                            "\u{2014} Not verified",
                            Style::default().fg(Color::DarkGray),
                        ),
                        Span::raw(" (press v to verify)"),
                    ]));
                }
            }

            if let Some(revoked) = self.is_revoked {
                text.push_line(Line::from(vec![
                    Span::styled(" Blacklist: ", Style::default().fg(Color::Cyan)),
                    if revoked {
                        Span::styled(
                            "\u{2717} Revoked",
                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
                        )
                    } else {
                        Span::styled(
                            "\u{2713} Not revoked",
                            Style::default().fg(Color::Green),
                        )
                    },
                ]));
            }

            text.push_line(Line::from(""));
            if let Some(u) = &self.user {
                text.push_line(Line::from(vec![
                    Span::styled(
                        "\u{25b6} Linked user: ",
                        Style::default().fg(Color::Cyan),
                    ),
                    Span::raw(format!("#{}", u.id)),
                    if let Some(email) = &u.email {
                        Span::raw(format!(" \u{2014} {email}"))
                    } else {
                        Span::raw("")
                    },
                ]));
                text.push_line(Line::from(Span::raw(format!(
                    "   Active tokens: {}",
                    u.active_tokens
                ))));
            } else if info.sub.is_some() {
                text.push_line(Line::from(Span::styled(
                    "   (press u to look up linked user from DB)",
                    Style::default().fg(Color::DarkGray),
                )));
            }
        }

        let body = Paragraph::new(text)
            .block(Block::default().borders(Borders::ALL).title(" Decoded "))
            .wrap(Wrap { trim: false });
        frame.render_widget(body, area);
    }

    fn render_help(&self, frame: &mut Frame, area: Rect) {
        let keys = vec![
            ("Enter", "decode"),
            ("v", "verify"),
            ("u", "user"),
            ("b", "blacklist"),
            ("Esc", "clear"),
        ];
        let spans: Vec<Span> = keys
            .iter()
            .flat_map(|(k, d)| {
                vec![
                    Span::styled(format!(" {k}"), Style::default().fg(Color::Cyan)),
                    Span::raw(format!(" {d}  ")),
                ]
            })
            .collect();
        frame.render_widget(
            Paragraph::new(Line::from(spans)).alignment(Alignment::Center),
            area,
        );
    }

    pub fn handle_key(&mut self, key: KeyEvent) -> AppAction {
        match key.code {
            KeyCode::Char(c) if !matches!(c, 'v' | 'u' | 'b') => {
                if c.is_ascii_graphic() || c == ' ' {
                    self.token_input.push(c);
                    self.error = None;
                }
                AppAction::None
            }
            KeyCode::Backspace => {
                self.token_input.pop();
                AppAction::None
            }
            KeyCode::Enter => {
                self.decode();
                AppAction::None
            }
            KeyCode::Esc => self.clear(),
            KeyCode::Char('v') => {
                if self.decoded.is_some() {
                    AppAction::AuthVerify
                } else {
                    AppAction::None
                }
            }
            KeyCode::Char('u') => {
                if self.decoded.is_some() {
                    AppAction::AuthLookupUser
                } else {
                    AppAction::None
                }
            }
            KeyCode::Char('b') => {
                if self.decoded.is_some() {
                    AppAction::AuthCheckBlacklist
                } else {
                    AppAction::None
                }
            }
            _ => AppAction::None,
        }
    }

    fn decode(&mut self) {
        use base64::Engine;

        let token = self.token_input.trim();
        if token.is_empty() {
            return;
        }

        self.error = None;
        self.decoded = None;
        self.verify_status = VerifyStatus::Untrusted;
        self.user = None;
        self.is_revoked = None;

        let header = match decode_header(token) {
            Ok(h) => h,
            Err(e) => {
                self.error = Some(format!("Invalid JWT header: {e}"));
                return;
            }
        };
        let header_val = serde_json::to_value(&header).unwrap_or(Value::Null);

        let parts: Vec<&str> = token.split('.').collect();
        if parts.len() != 3 {
            self.error = Some(
                "Invalid JWT format: expected 3 dot-separated segments".into(),
            );
            return;
        }

        let payload_bytes = match base64::engine::general_purpose::URL_SAFE_NO_PAD
            .decode(parts[1])
        {
            Ok(b) => b,
            Err(e) => {
                self.error = Some(format!("Invalid base64 payload: {e}"));
                return;
            }
        };

        let payload: Value = match serde_json::from_slice(&payload_bytes) {
            Ok(v) => v,
            Err(e) => {
                self.error = Some(format!("Invalid JSON payload: {e}"));
                return;
            }
        };

        let sub = payload.get("sub").and_then(|v| v.as_str()).map(String::from);
        let exp = payload.get("exp").and_then(|v| v.as_i64());
        let iat = payload.get("iat").and_then(|v| v.as_i64());

        self.decoded = Some(DecodedInfo {
            header: header_val,
            payload,
            sub,
            exp,
            iat,
        });

        let display = if token.len() > 50 {
            format!("{}...{}", &token[..25], &token[token.len() - 12..])
        } else {
            token.to_string()
        };
        self.recent_tokens.push_front(display);
        if self.recent_tokens.len() > MAX_RECENT {
            self.recent_tokens.pop_back();
        }
    }

    fn clear(&mut self) -> AppAction {
        self.token_input.clear();
        self.decoded = None;
        self.verify_status = VerifyStatus::Untrusted;
        self.user = None;
        self.is_revoked = None;
        self.error = None;
        AppAction::None
    }

    pub async fn verify_signature(&mut self, _pool: &PgPool) {
        let token = self.token_input.trim();
        if token.is_empty() {
            return;
        }

        let secret = std::env::var("JWT_SECRET").ok();

        match secret {
            Some(s) if !s.is_empty() => {
                match rok_auth::Auth::new(rok_auth::AuthConfig {
                    secret: s,
                    ..Default::default()
                }) {
                    Ok(auth) => match auth.verify(token) {
                        Ok(claims) => {
                            let token_type = if claims.has_role("totp_pending") {
                                "TOTP pending".into()
                            } else {
                                "Access token".into()
                            };
                            let age = chrono::Utc::now().timestamp() - claims.iat;
                            self.verify_status = VerifyStatus::Verified {
                                token_type,
                                age_human: format_duration(age),
                            };
                            self.error = None;
                        }
                        Err(e) => {
                            let reason = match &e {
                                rok_auth::AuthError::TokenExpired => "Token expired".into(),
                                rok_auth::AuthError::InvalidToken => {
                                    "Invalid signature or malformed".into()
                                }
                                _ => format!("{e}"),
                            };
                            self.verify_status = VerifyStatus::Invalid(reason);
                        }
                    },
                    Err(e) => {
                        self.error = Some(format!("Auth init error: {e}"));
                    }
                }
            }
            _ => {
                self.error = Some("JWT_SECRET not set \u{2014} cannot verify signature".into());
            }
        }
    }

    pub async fn lookup_user(&mut self, pool: &PgPool) {
        let sub = self.decoded.as_ref().and_then(|d| d.sub.clone());
        let Some(sub) = sub else {
            self.error = Some("No 'sub' claim in token payload".into());
            return;
        };

        match sqlx::query_as::<_, (String, Option<String>)>(
            "SELECT id::text, email FROM users \
             WHERE id::text = $1 OR id = $1::bigint \
             LIMIT 1",
        )
        .bind(&sub)
        .fetch_optional(pool)
        .await
        {
            Ok(Some((id, email))) => {
                let token_count: i64 =
                    sqlx::query_scalar("SELECT COUNT(*) FROM personal_access_tokens \
                                        WHERE tokenable_id = $1")
                        .bind(&id.parse::<i64>().unwrap_or(0))
                        .fetch_one(pool)
                        .await
                        .unwrap_or(0);

                self.user = Some(UserRecord {
                    id,
                    email,
                    active_tokens: token_count as usize,
                });
                self.error = None;
            }
            Ok(None) => {
                self.error = Some(format!("User #{sub} not found in users table"));
            }
            Err(e) => {
                self.error = Some(format!("User lookup failed: {e}"));
            }
        }
    }

    pub async fn check_blacklist(&mut self, pool: &PgPool) {
        let token = self.token_input.trim();
        if token.is_empty() {
            return;
        }
        self.is_revoked = Some(rok_auth::TokenBlacklist::is_revoked(pool, token).await);
    }
}

fn format_duration(secs: i64) -> String {
    let abs = secs.unsigned_abs();
    let days = abs / 86400;
    let hours = (abs % 86400) / 3600;
    let minutes = (abs % 3600) / 60;
    let secs = abs % 60;
    let mut parts = Vec::new();
    if days > 0 {
        parts.push(format!("{days}d"));
    }
    if hours > 0 {
        parts.push(format!("{hours}h"));
    }
    if minutes > 0 {
        parts.push(format!("{minutes}m"));
    }
    if secs > 0 || parts.is_empty() {
        parts.push(format!("{secs}s"));
    }
    parts.join(" ")
}
