mod app;
mod ui;

use std::io;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant, SystemTime};

use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::execute;
use ratatui::prelude::*;

use crate::{policy, trace, threat::state::SessionState};
use app::{App, Filter};

/// Run the dashboard TUI.
pub fn run(session: Option<String>) -> i32 {
    let cwd = std::env::current_dir().unwrap_or_default();
    let loaded_policy = policy::loader::load_policy_or_defaults(&cwd);
    let trace_dir = cwd.join(&loaded_policy.trace.directory);
    let state_dir = cwd.join(".railyard/state");

    // Resolve session ID
    let session_id = match session {
        Some(id) => id,
        None => match find_latest_session(&trace_dir, &state_dir) {
            Some(id) => id,
            None => {
                eprintln!("  No active session found. Waiting for activity...");
                match wait_for_session(&trace_dir, &state_dir) {
                    Some(id) => id,
                    None => {
                        eprintln!("  Interrupted.");
                        return 1;
                    }
                }
            }
        },
    };

    match run_tui(&session_id, &trace_dir, &state_dir) {
        Ok(_) => 0,
        Err(e) => {
            eprintln!("Dashboard error: {}", e);
            1
        }
    }
}

/// Find the most recently modified session (by trace file or state file).
fn find_latest_session(trace_dir: &Path, state_dir: &Path) -> Option<String> {
    let mut latest: Option<(String, SystemTime)> = None;

    for dir in &[trace_dir, state_dir] {
        if let Ok(entries) = std::fs::read_dir(dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                let ext = path.extension().and_then(|e| e.to_str());
                if ext == Some("jsonl") || ext == Some("json") {
                    if let (Some(stem), Ok(meta)) = (path.file_stem(), entry.metadata()) {
                        if let Ok(modified) = meta.modified() {
                            let name = stem.to_string_lossy().to_string();
                            if latest.as_ref().map_or(true, |(_, t)| modified > *t) {
                                latest = Some((name, modified));
                            }
                        }
                    }
                }
            }
        }
    }

    latest.map(|(id, _)| id)
}

/// Block until a session appears (poll every second).
fn wait_for_session(trace_dir: &Path, state_dir: &Path) -> Option<String> {
    loop {
        if let Some(id) = find_latest_session(trace_dir, state_dir) {
            return Some(id);
        }
        std::thread::sleep(Duration::from_secs(1));
        // Check for Ctrl+C
        if event::poll(Duration::from_millis(0)).unwrap_or(false) {
            if let Ok(Event::Key(key)) = event::read() {
                if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
                    return None;
                }
            }
        }
    }
}

fn run_tui(session_id: &str, trace_dir: &Path, state_dir: &Path) -> io::Result<()> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    let mut app = App::new(session_id.to_string());
    let tick_rate = Duration::from_millis(500);
    let mut last_poll = Instant::now();
    let trace_dir = trace_dir.to_path_buf();
    let state_dir = state_dir.to_path_buf();

    // Initial load
    reload_data(&mut app, &trace_dir, &state_dir);

    loop {
        terminal.draw(|f| ui::draw(f, &mut app))?;

        let timeout = tick_rate.saturating_sub(last_poll.elapsed());
        if event::poll(timeout)? {
            if let Event::Key(key) = event::read()? {
                match key.code {
                    KeyCode::Char('q') => break,
                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => break,
                    KeyCode::Up | KeyCode::Char('k') => {
                        app.scroll_up();
                        app.tailing = false;
                    }
                    KeyCode::Down | KeyCode::Char('j') => {
                        app.scroll_down();
                    }
                    KeyCode::Char('G') => {
                        app.jump_to_end();
                        app.tailing = true;
                    }
                    KeyCode::Char('g') => {
                        app.jump_to_start();
                        app.tailing = false;
                    }
                    KeyCode::Enter => {
                        app.toggle_expand();
                    }
                    KeyCode::Esc => {
                        if app.expanded.is_some() {
                            app.expanded = None;
                        } else if app.filter != Filter::All {
                            app.filter = Filter::All;
                            app.update_filtered();
                        }
                    }
                    KeyCode::Char('f') => {
                        app.cycle_filter();
                    }
                    KeyCode::Char('?') => {
                        app.show_help = !app.show_help;
                    }
                    _ => {}
                }
            }
            if let Event::Resize(_, _) = event::read().unwrap_or(Event::FocusLost) {
                // Terminal will re-draw on next iteration
            }
        }

        if last_poll.elapsed() >= tick_rate {
            reload_data(&mut app, &trace_dir, &state_dir);
            last_poll = Instant::now();
        }
    }

    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
    terminal.show_cursor()?;

    Ok(())
}

fn reload_data(app: &mut App, trace_dir: &PathBuf, state_dir: &PathBuf) {
    // Reload traces
    if let Ok(entries) = trace::logger::read_traces(trace_dir, &app.session_id) {
        let was_at_end = app.tailing;
        app.entries = entries;
        app.update_filtered();
        if was_at_end {
            app.jump_to_end();
        }
    }

    // Reload session state
    if state_dir.exists() {
        app.session_state = Some(SessionState::load(state_dir, &app.session_id));
    }

    app.last_update = Instant::now();
}
