// End-to-end tests for unnamed buffer persistence across sessions (#1148)

use crate::common::harness::{EditorTestHarness, HarnessOptions};
use crossterm::event::{KeyCode, KeyModifiers};
use fresh::config::Config;
use fresh::config_io::DirectoryContext;
use tempfile::TempDir;

/// Test that an unnamed buffer with content survives save/restore cycle
#[test]
fn test_unnamed_buffer_survives_save_restore_cycle() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let dir_context = DirectoryContext::for_testing(temp_dir.path());

    // First session: create unnamed buffer, type content, save workspace + flush
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        harness.new_buffer().unwrap();
        harness.type_text("scratch notes here").unwrap();
        harness.render().unwrap();

        // Verify content appears on screen
        harness.assert_screen_contains("scratch notes here");

        // Clean shutdown (mirrors production exit path)
        harness.shutdown(true).unwrap();
    }

    // Second session: restore and verify unnamed buffer content
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        // Startup without CLI args (mirrors production startup path)
        let restored = harness.startup(true, &[]).unwrap();
        assert!(restored, "Session should have been restored");
        harness.assert_screen_contains("scratch notes here");
    }
}

/// Test that unnamed buffers and file-backed buffers are restored together
#[test]
fn test_unnamed_and_file_buffers_restored_together() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("a.txt");
    std::fs::write(&file1, "file A content").unwrap();

    let dir_context = DirectoryContext::for_testing(temp_dir.path());

    // First session: open file + create unnamed buffer
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        harness.open_file(&file1).unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("file A content");

        harness.new_buffer().unwrap();
        harness.type_text("unnamed content").unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("unnamed content");

        // Clean shutdown (mirrors production exit path)
        harness.shutdown(true).unwrap();
    }

    // Second session: restore both
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        // Startup without CLI args (mirrors production startup path)
        let restored = harness.startup(true, &[]).unwrap();
        assert!(restored, "Session should have been restored");

        // Switch to the file buffer and verify it's visible
        harness.open_file(&file1).unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("file A content");
    }
}

/// Test that quit with only unnamed modified buffers skips the discard prompt
#[test]
fn test_quit_unnamed_only_skips_prompt() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let mut config = Config::default();
    config.editor.hot_exit = true;

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Create unnamed buffer with content
    harness.new_buffer().unwrap();
    harness.type_text("some scratch text").unwrap();
    harness.render().unwrap();

    // Send Ctrl+Q to quit - should quit immediately without prompt
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // No "unsaved changes" prompt should appear; editor should quit
    harness.assert_screen_not_contains("unsaved changes");
    assert!(
        harness.should_quit(),
        "Editor should quit without prompt when only unnamed buffers are modified"
    );
}

/// Test that hot_exit=false preserves old behavior (shows quit prompt)
#[test]
fn test_persist_disabled_shows_quit_prompt() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let mut config = Config::default();
    config.editor.hot_exit = false;

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Create unnamed buffer with content
    harness.new_buffer().unwrap();
    harness.type_text("some scratch text").unwrap();
    harness.render().unwrap();

    // Send Ctrl+Q to quit - should show prompt
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Should see the discard prompt on screen
    harness.assert_screen_contains("unsaved changes");
    assert!(
        !harness.should_quit(),
        "Editor should show prompt when hot_exit is disabled"
    );
}

/// Test that quit with mixed modified buffers still prompts when hot_exit is off
#[test]
fn test_quit_mixed_modified_still_prompts() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("test.txt");
    std::fs::write(&file1, "original").unwrap();

    let mut config = Config::default();
    config.editor.hot_exit = false;

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Open and modify a file-backed buffer
    harness.open_file(&file1).unwrap();
    harness.type_text("edit").unwrap();
    harness.render().unwrap();

    // Also create an unnamed buffer
    harness.new_buffer().unwrap();
    harness.type_text("scratch").unwrap();
    harness.render().unwrap();

    // Send Ctrl+Q to quit - should show prompt because of file-backed modification
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Should see the discard prompt
    harness.assert_screen_contains("unsaved changes");
    assert!(
        !harness.should_quit(),
        "Editor should show prompt when file-backed buffers are modified"
    );
}

/// Test that hot exit preserves unsaved changes in file-backed buffers across sessions
#[test]
fn test_hot_exit_restores_unsaved_file_changes() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("hello.txt");
    std::fs::write(&file1, "original content").unwrap();

    let dir_context = DirectoryContext::for_testing(temp_dir.path());

    // First session: open file, modify it, then flush+save
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        harness.open_file(&file1).unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("original content");

        // Move to end and add text
        harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
        harness.type_text(" EDITED").unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("EDITED");

        // Clean shutdown (mirrors production exit path)
        harness.shutdown(true).unwrap();
    }

    // Verify file on disk is unchanged
    let on_disk = std::fs::read_to_string(&file1).unwrap();
    assert_eq!(
        on_disk, "original content",
        "File on disk should be unchanged"
    );

    // Second session: restore and verify unsaved changes are back
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        // Startup without CLI args (mirrors production startup path)
        let restored = harness.startup(true, &[]).unwrap();
        assert!(restored, "Session should have been restored");
        harness.assert_screen_contains("EDITED");
    }
}

/// Test that hot exit restores unsaved changes when file is opened via CLI
/// (i.e. without workspace restore - simulates `fresh file.txt` workflow)
#[test]
fn test_hot_exit_restores_without_workspace() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("hello.txt");
    std::fs::write(&file1, "original content").unwrap();

    let dir_context = DirectoryContext::for_testing(temp_dir.path());

    // First session: open file, modify it, end recovery (simulating quit without workspace save)
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        harness.open_file(&file1).unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("original content");

        // Move to end and add text
        harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
        harness.type_text(" EDITED").unwrap();
        harness.render().unwrap();
        harness.assert_screen_contains("EDITED");

        // Shutdown without workspace save (simulates CLI file workflow where
        // user quits without saving workspace)
        harness.shutdown(false).unwrap();
    }

    // Verify file on disk is unchanged
    let on_disk = std::fs::read_to_string(&file1).unwrap();
    assert_eq!(
        on_disk, "original content",
        "File on disk should be unchanged"
    );

    // Second session: open file via CLI arg WITHOUT workspace restore.
    // The startup() method will queue the file, schedule hot exit recovery,
    // and process pending opens — mirroring the real production path.
    {
        let mut config = Config::default();
        config.editor.hot_exit = true;

        let mut harness = EditorTestHarness::create(
            80,
            24,
            HarnessOptions::new()
                .with_config(config)
                .with_working_dir(project_dir.clone())
                .with_shared_dir_context(dir_context.clone())
                .without_empty_plugins_dir(),
        )
        .unwrap();

        // Startup with CLI file, no workspace restore (mirrors `fresh hello.txt`)
        harness.startup(false, &[file1.clone()]).unwrap();
        harness.assert_screen_contains("EDITED");
    }
}

/// Test that hot exit quit shows prompt with "recoverable" option
#[test]
fn test_hot_exit_quit_shows_recoverable_option() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("test.txt");
    std::fs::write(&file1, "original").unwrap();

    let mut config = Config::default();
    config.editor.hot_exit = true;

    let mut harness = EditorTestHarness::create(
        120,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Open and modify the file
    harness.open_file(&file1).unwrap();
    harness.type_text("changes").unwrap();
    harness.render().unwrap();

    // Send Ctrl+Q — should show prompt with "recoverable" option
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    harness.assert_screen_contains("unsaved changes");
    harness.assert_screen_contains("recoverable");
    assert!(
        !harness.should_quit(),
        "Editor should prompt, not quit immediately"
    );

    // Press 'q' then Enter to quit without saving (recoverable)
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::NONE)
        .unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    assert!(
        harness.should_quit(),
        "Editor should quit after choosing recoverable quit"
    );
}

/// Test that quit prompt save option saves and quits
#[test]
fn test_quit_save_and_quit() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("test.txt");
    std::fs::write(&file1, "original").unwrap();

    let mut config = Config::default();
    config.editor.hot_exit = false;

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Open and modify the file
    harness.open_file(&file1).unwrap();
    harness.render().unwrap();
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness.type_text(" SAVED").unwrap();
    harness.render().unwrap();

    // Quit with Ctrl+Q — should show prompt
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    assert!(!harness.should_quit());

    // Press 's' then Enter to save and quit
    harness
        .send_key(KeyCode::Char('s'), KeyModifiers::NONE)
        .unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    assert!(
        harness.should_quit(),
        "Editor should quit after save-and-quit"
    );

    // File on disk should have the modifications
    assert_eq!(std::fs::read_to_string(&file1).unwrap(), "original SAVED");
}

/// Test that auto-save saves file-backed buffers to disk on exit
#[test]
fn test_auto_save_saves_on_exit() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("test.txt");
    std::fs::write(&file1, "original").unwrap();

    let mut config = Config::default();
    config.editor.auto_save_enabled = true;

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Open and modify the file
    harness.open_file(&file1).unwrap();
    harness.render().unwrap();
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness.type_text(" MODIFIED").unwrap();
    harness.render().unwrap();

    // Verify file on disk is unchanged before exit
    assert_eq!(std::fs::read_to_string(&file1).unwrap(), "original");

    // save_all_on_exit should save to disk (simulates quit with auto_save)
    let saved = harness.editor_mut().save_all_on_exit().unwrap();
    assert_eq!(saved, 1, "Should have saved one buffer");

    // File on disk should now have the modifications
    assert_eq!(
        std::fs::read_to_string(&file1).unwrap(),
        "original MODIFIED"
    );
}

/// Test that auto-save on exit skips the quit prompt
#[test]
fn test_auto_save_skips_quit_prompt() {
    let temp_dir = TempDir::new().unwrap();
    let project_dir = temp_dir.path().join("project");
    std::fs::create_dir(&project_dir).unwrap();

    let file1 = project_dir.join("test.txt");
    std::fs::write(&file1, "original").unwrap();

    let mut config = Config::default();
    config.editor.auto_save_enabled = true;
    config.editor.hot_exit = false; // only auto-save, no hot exit

    let mut harness = EditorTestHarness::create(
        80,
        24,
        HarnessOptions::new()
            .with_config(config)
            .with_working_dir(project_dir)
            .without_empty_plugins_dir(),
    )
    .unwrap();

    // Open and modify the file
    harness.open_file(&file1).unwrap();
    harness.render().unwrap();
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness.type_text(" MODIFIED").unwrap();
    harness.render().unwrap();

    // Quit — should not prompt because auto_save is enabled
    harness
        .send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    assert!(
        harness.should_quit(),
        "Editor should quit without prompt when auto_save is enabled"
    );
}
