//! The SSH registration form.
//!
//! A simple terminal UI that allows unauthenticated users to register an
//! account by providing username, email, and password. The SSH public key is
//! taken from the verified connection and displayed as read-only.

use std::sync::Arc;

use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::{ExecutableCommand, terminal};
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::{Frame, Terminal};
use russh::keys::ssh_key;
use sea_orm::ActiveValue::Set;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
use tokio::sync::{RwLock, mpsc};
use tracing::info;
use uuid::Uuid;

use crate::AppState;
use crate::auth::hash_password;
use crate::entities::{ssh_keys, users};

/// The editable fields in the registration form.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Field {
    Username,
    Email,
    Password,
    ConfirmPassword,
}

impl Field {
    fn next(self) -> Self {
        match self {
            Field::Username => Field::Email,
            Field::Email => Field::Password,
            Field::Password => Field::ConfirmPassword,
            Field::ConfirmPassword => Field::Username,
        }
    }

    fn previous(self) -> Self {
        match self {
            Field::Username => Field::ConfirmPassword,
            Field::Email => Field::Username,
            Field::Password => Field::Email,
            Field::ConfirmPassword => Field::Password,
        }
    }

    fn label(self) -> &'static str {
        match self {
            Field::Username => "Username",
            Field::Email => "Email",
            Field::Password => "Password",
            Field::ConfirmPassword => "Confirm Password",
        }
    }
}

/// The state of the registration form.
struct FormState {
    username: String,
    email: String,
    password: String,
    confirm_password: String,
    current_field: Field,
    error: Option<String>,
    submitted: bool,
}

impl FormState {
    fn new() -> Self {
        Self {
            username: String::new(),
            email: String::new(),
            password: String::new(),
            confirm_password: String::new(),
            current_field: Field::Username,
            error: None,
            submitted: false,
        }
    }

    fn current_value_mut(&mut self) -> &mut String {
        match self.current_field {
            Field::Username => &mut self.username,
            Field::Email => &mut self.email,
            Field::Password => &mut self.password,
            Field::ConfirmPassword => &mut self.confirm_password,
        }
    }

    fn handle_key(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Tab | KeyCode::Down => {
                self.current_field = self.current_field.next();
                self.error = None;
            }
            KeyCode::BackTab | KeyCode::Up => {
                self.current_field = self.current_field.previous();
                self.error = None;
            }
            KeyCode::Enter => {
                self.submitted = true;
            }
            KeyCode::Char(c) => {
                self.current_value_mut().push(c);
            }
            KeyCode::Backspace => {
                self.current_value_mut().pop();
            }
            _ => {}
        }
    }
}

/// Draws the registration form UI.
fn draw(f: &mut Frame, form: &FormState, verified_key: &Option<String>) {
    let size = f.area();

    // Clear the screen
    f.render_widget(Clear, size);

    // Create a centered layout
    let vertical = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1),
            Constraint::Min(10),
            Constraint::Length(1),
        ])
        .split(size);

    let horizontal = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage(10),
            Constraint::Percentage(80),
            Constraint::Percentage(10),
        ])
        .split(vertical[1]);

    let area = horizontal[1];

    // Create the form block
    let block = Block::default()
        .title(" Register a new account ")
        .borders(Borders::ALL)
        .style(Style::default().fg(Color::White));

    let inner = block.inner(area);
    f.render_widget(block, area);

    // Create form fields
    let fields = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3), // Username
            Constraint::Length(3), // Email
            Constraint::Length(3), // Password
            Constraint::Length(3), // Confirm
            Constraint::Length(1), // Spacer
            Constraint::Length(5), // SSH Key (read-only display)
            Constraint::Length(3), // Error/Submit
            Constraint::Min(0),    // Spacer
        ])
        .split(inner);

    // Draw each field
    draw_field(
        f,
        fields[0],
        Field::Username,
        &form.username,
        form.current_field == Field::Username,
        false,
    );
    draw_field(
        f,
        fields[1],
        Field::Email,
        &form.email,
        form.current_field == Field::Email,
        false,
    );
    draw_field(
        f,
        fields[2],
        Field::Password,
        &form.password,
        form.current_field == Field::Password,
        true,
    );
    draw_field(
        f,
        fields[3],
        Field::ConfirmPassword,
        &form.confirm_password,
        form.current_field == Field::ConfirmPassword,
        true,
    );

    // Draw SSH key as read-only section
    draw_ssh_key_display(f, fields[5], verified_key);

    // Draw error or submit button
    if let Some(error) = &form.error {
        let error_text = Paragraph::new(error.as_str()).style(Style::default().fg(Color::Red));
        f.render_widget(error_text, fields[6]);
    } else {
        let submit = Paragraph::new("Press ENTER to submit, Tab to navigate, Ctrl+C to cancel")
            .style(Style::default().fg(Color::DarkGray));
        f.render_widget(submit, fields[6]);
    }
}

/// Draws a single form field.
fn draw_field(f: &mut Frame, area: Rect, field: Field, value: &str, focused: bool, masked: bool) {
    let label = field.label();
    let display_value = if masked {
        "•".repeat(value.len())
    } else {
        value.to_string()
    };

    let border_style = if focused {
        Style::default().fg(Color::Cyan)
    } else {
        Style::default().fg(Color::DarkGray)
    };

    let block = Block::default()
        .title(format!(" {} ", label))
        .borders(Borders::ALL)
        .style(border_style);

    let inner = block.inner(area);
    f.render_widget(block, area);

    let text = Paragraph::new(display_value);
    f.render_widget(text, inner);

    // Draw cursor if focused
    if focused {
        let cursor_x = inner.x + value.len() as u16;
        let cursor_y = inner.y;
        f.set_cursor_position((cursor_x, cursor_y));
    }
}

/// Draws the SSH public key as a read-only display.
fn draw_ssh_key_display(f: &mut Frame, area: Rect, verified_key: &Option<String>) {
    let block = Block::default()
        .title(" SSH Public Key (from this connection) ")
        .borders(Borders::ALL)
        .style(Style::default().fg(Color::DarkGray));

    let inner = block.inner(area);
    f.render_widget(block, area);

    let display_value = if let Some(key) = verified_key {
        if key.len() > 30 {
            // Ellide long keys: show first 10 and last 10 chars
            format!("{}...{}", &key[..10], &key[key.len() - 10..])
        } else {
            key.clone()
        }
    } else {
        "No SSH key provided".to_string()
    };

    let text = Paragraph::new(display_value).style(Style::default().fg(Color::DarkGray));
    f.render_widget(text, inner);
}

/// Validates the form and performs registration if valid.
async fn validate_and_register(
    form: &FormState,
    verified_key: &Option<ssh_key::PublicKey>,
    state: Arc<RwLock<AppState>>,
) -> Result<users::Model, String> {
    if form.username.trim().is_empty() {
        return Err("Username is required".to_string());
    }

    if form.email.trim().is_empty() {
        return Err("Email is required".to_string());
    }

    if form.password.len() < 8 {
        return Err("Password must be at least 8 characters".to_string());
    }

    if form.password != form.confirm_password {
        return Err("Passwords do not match".to_string());
    }

    // The SSH key must be provided from the verified connection
    let ssh_key_to_register = verified_key
        .as_ref()
        .ok_or_else(|| "No SSH key provided. Please reconnect with an SSH key.".to_string())?;

    // Check if username already exists
    let state_read = state.read().await;
    let db = &state_read.db;

    // Check registration mode gate before proceeding
    crate::registration::check_mode_allows_registration(db, None).await.map_err(|e| {
        if matches!(e, crate::registration::RegisterError::InviteRequired) {
            "Registration on this server is invite-only. Please use the web UI with an invite link.".to_string()
        } else {
            e.to_string()
        }
    })?;

    let existing_user = users::Entity::find()
        .filter(users::Column::Name.eq(&form.username))
        .one(db)
        .await
        .map_err(|e| format!("Database error: {}", e))?;

    if existing_user.is_some() {
        return Err("Username already taken".to_string());
    }

    // Check if email already exists
    let existing_email = users::Entity::find()
        .filter(users::Column::Email.eq(&form.email))
        .one(db)
        .await
        .map_err(|e| format!("Database error: {}", e))?;

    if existing_email.is_some() {
        return Err("Email already registered".to_string());
    }

    // Hash the password
    let password_hash =
        hash_password(&form.password).map_err(|e| format!("Failed to hash password: {}", e))?;

    // Create the user
    let user = users::ActiveModel {
        uuid: Set(Uuid::new_v4()),
        name: Set(form.username.trim().to_string()),
        email: Set(form.email.trim().to_string()),
        password_hash: Set(password_hash),
        ..Default::default()
    };

    let user = user
        .insert(db)
        .await
        .map_err(|e| format!("Failed to create user: {}", e))?;

    info!(user_id = user.id, user = %user.name, "user registered via SSH TUI");

    // Add the SSH key
    let fingerprint = ssh_key_to_register
        .fingerprint(russh::keys::HashAlg::Sha256)
        .to_string();
    let public_key_openssh = ssh_key_to_register
        .to_openssh()
        .map_err(|e| format!("Failed to serialize key: {}", e))?;

    let ssh_key_model = ssh_keys::ActiveModel {
        user_id: Set(user.id),
        fingerprint: Set(fingerprint.clone()),
        public_key: Set(public_key_openssh),
        ..Default::default()
    };

    ssh_key_model
        .insert(db)
        .await
        .map_err(|e| format!("Failed to add SSH key: {}", e))?;

    info!(user_id = user.id, fingerprint = %fingerprint, "SSH key added during registration");

    Ok(user)
}

/// Runs the registration TUI and returns the created user if successful.
pub async fn run_registration_form(
    input_rx: &mut mpsc::UnboundedReceiver<Vec<u8>>,
    output_tx: mpsc::UnboundedSender<Vec<u8>>,
    verified_key: Option<ssh_key::PublicKey>,
    state: Arc<RwLock<AppState>>,
) -> Result<Option<users::Model>, Box<dyn std::error::Error + Send + Sync>> {
    let writer = super::ChannelWriter::new(output_tx);
    let backend = CrosstermBackend::new(writer);
    let mut terminal = Terminal::new(backend)?;

    // Clear the terminal before entering
    terminal
        .backend_mut()
        .execute(terminal::Clear(terminal::ClearType::All))?;

    // Convert verified key to string for display
    let verified_key_str = verified_key
        .as_ref()
        .map(|k| k.to_openssh().unwrap_or_else(|_| "Invalid key".to_string()));

    let mut form = FormState::new();

    loop {
        // Draw the UI
        terminal.draw(|f| {
            draw(f, &form, &verified_key_str);
        })?;

        // Wait for input
        if let Some(data) = input_rx.recv().await {
            // Parse the input as key events
            for event in parse_key_events(&data) {
                if let Event::Key(key) = event {
                    // Ctrl+C or Esc cancels the form
                    if key.code == KeyCode::Esc
                        || (key.code == KeyCode::Char('c')
                            && key.modifiers.contains(KeyModifiers::CONTROL))
                    {
                        cleanup(&mut terminal)?;
                        return Ok(None);
                    }

                    form.handle_key(key);

                    if form.submitted {
                        match validate_and_register(&form, &verified_key, state.clone()).await {
                            Ok(user) => {
                                cleanup(&mut terminal)?;
                                return Ok(Some(user));
                            }
                            Err(error) => {
                                form.error = Some(error);
                                form.submitted = false;
                            }
                        }
                    }
                }
            }
        }
    }
}

/// Cleans up the terminal state.
fn cleanup(
    terminal: &mut Terminal<CrosstermBackend<super::ChannelWriter>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    terminal::disable_raw_mode()?;
    terminal
        .backend_mut()
        .execute(terminal::Clear(terminal::ClearType::All))?;
    terminal
        .backend_mut()
        .execute(terminal::LeaveAlternateScreen)?;
    Ok(())
}

/// Parses raw bytes into key events.
fn parse_key_events(data: &[u8]) -> Vec<Event> {
    let mut events = Vec::new();
    let mut i = 0;

    while i < data.len() {
        // Check for escape sequences
        if data[i] == 0x1b {
            if i + 2 < data.len() && data[i + 1] == b'[' {
                // CSI sequence
                match data[i + 2] {
                    b'A' => events.push(Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE))),
                    b'B' => {
                        events.push(Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)))
                    }
                    b'C' => events.push(Event::Key(KeyEvent::new(
                        KeyCode::Right,
                        KeyModifiers::NONE,
                    ))),
                    b'D' => {
                        events.push(Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE)))
                    }
                    b'Z' => events.push(Event::Key(KeyEvent::new(
                        KeyCode::BackTab,
                        KeyModifiers::NONE,
                    ))),
                    _ => {}
                }
                i += 3;
                continue;
            }
            i += 1;
            continue;
        }

        // Regular character
        let byte = data[i];
        if byte < 0x80 {
            // ASCII
            if byte == 0x0d {
                // Enter
                events.push(Event::Key(KeyEvent::new(
                    KeyCode::Enter,
                    KeyModifiers::NONE,
                )));
            } else if byte == 0x7f || byte == 0x08 {
                // Backspace
                events.push(Event::Key(KeyEvent::new(
                    KeyCode::Backspace,
                    KeyModifiers::NONE,
                )));
            } else if byte == 0x09 {
                // Tab
                events.push(Event::Key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)));
            } else if byte == 0x03 {
                // Ctrl+C
                events.push(Event::Key(KeyEvent::new(
                    KeyCode::Char('c'),
                    KeyModifiers::CONTROL,
                )));
            } else if byte == 0x1b {
                // Escape
                events.push(Event::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)));
            } else if (0x20..0x7f).contains(&byte) {
                // Printable ASCII
                events.push(Event::Key(KeyEvent::new(
                    KeyCode::Char(byte as char),
                    KeyModifiers::NONE,
                )));
            }
            i += 1;
        } else {
            // UTF-8 multi-byte
            // For simplicity, skip non-ASCII for now
            i += 1;
        }
    }

    events
}
