use crate::common::harness::EditorTestHarness;

/// Test that the prompt is rendered correctly
#[test]
fn test_prompt_rendering() {
    use crossterm::event::{KeyCode, KeyModifiers};
    let mut harness = EditorTestHarness::new(80, 24).unwrap();

    // Trigger the open file prompt with Ctrl+O
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Check that the prompt is visible in the status bar area (bottom line)
    let _screen = harness.screen_to_string();
    harness.assert_screen_contains("Open:");

    // Check the prompt styling
    let buffer = harness.buffer();
    let status_y = buffer.area.height - 1; // Status bar is at the bottom

    // Check a cell in the status bar has black background (default prompt color)
    let first_cell_pos = buffer.index_of(0, status_y);
    let first_cell = &buffer.content[first_cell_pos];
    assert_eq!(
        first_cell.bg,
        ratatui::style::Color::Black,
        "Prompt should have black background"
    );
}

/// Test prompt input handling (typing, backspace, cursor movement)
#[test]
fn test_prompt_input_handling() {
    use crossterm::event::{KeyCode, KeyModifiers};
    // Use harness with temp project so file paths are relative
    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();

    // Trigger the open file prompt with Ctrl+O
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.assert_screen_contains("Open:");

    // Type some text
    harness.type_text("test.txt").unwrap();
    harness.assert_screen_contains("test.txt");

    // Test backspace
    harness
        .send_key(KeyCode::Backspace, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
    harness.assert_screen_contains("test.tx");
    harness.assert_screen_not_contains("test.txt");

    // Type more
    harness.type_text("t2").unwrap();
    harness.assert_screen_contains("test.txt2");

    // Test Home (move cursor to start)
    harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();

    // Type at the beginning
    harness.type_text("my_").unwrap();
    harness.assert_screen_contains("my_test.txt2");

    // Test End (move cursor to end)
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness.type_text("!").unwrap();
    harness.assert_screen_contains("my_test.txt2!");
}

/// Test canceling the prompt with Escape
#[test]
fn test_prompt_cancel() {
    use crossterm::event::{KeyCode, KeyModifiers};
    // Use harness with temp project so file paths are relative
    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();

    // Trigger the open file prompt
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.assert_screen_contains("Open:");

    // Type some text (relative path)
    harness.type_text("test.txt").unwrap();
    harness.assert_screen_contains("test.txt");

    // Cancel with Escape
    harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
    harness.render().unwrap();

    // Prompt should be gone, and "Canceled" message should appear
    harness.assert_screen_not_contains("Open:");
    harness.assert_screen_contains("Canceled");
}

/// Test the complete open file workflow
#[test]
fn test_open_file_workflow() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;

    use tempfile::TempDir;

    // Create a temporary directory and file
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("test_prompt.txt");
    fs::write(&file_path, "Hello from prompt test!").unwrap();

    let mut harness = EditorTestHarness::new(80, 24).unwrap();

    // Trigger the open file prompt
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.assert_screen_contains("Open:");

    // Type the file path
    let path_str = file_path.to_str().unwrap();
    harness.type_text(path_str).unwrap();

    // Confirm with Enter
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Check that the file was opened
    harness.assert_screen_not_contains("Open:");

    // Check that the file content is displayed
    // Note: File content display may require additional renders after async file load
    harness.assert_screen_contains("Hello from prompt test!");

    // Check that the filename appears in the status bar
    harness.assert_screen_contains("test_prompt.txt");
}

/// Test opening a non-existent file creates an unsaved buffer
#[test]
fn test_open_nonexistent_file() {
    // Use harness with temp project so file paths are relative
    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();
    let project_dir = harness.project_dir().unwrap();
    let new_file_path = project_dir.join("new_file.txt");

    // Open non-existent file directly (via open_file, not file picker)
    harness.open_file(&new_file_path).unwrap();

    // Should NOT show an error - should open as unsaved buffer
    harness.assert_screen_not_contains("Error opening file");

    // Should show the filename in the tab/status bar
    harness.assert_screen_contains("new_file.txt");

    // Buffer should be empty
    assert_eq!(harness.get_buffer_content().unwrap(), "");

    // Should show "Opened" message
    harness.assert_screen_contains("Opened");
}

/// Test that opening a non-existent file allows editing and saving
#[test]
fn test_open_nonexistent_file_edit_and_save() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;

    // Use harness with temp project so file paths are relative
    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();
    let project_dir = harness.project_dir().unwrap();
    let new_file_path = project_dir.join("created_file.txt");

    // Verify file doesn't exist yet
    assert!(!new_file_path.exists());

    // Open non-existent file directly (via open_file, not file picker)
    harness.open_file(&new_file_path).unwrap();

    // Should open successfully
    harness.assert_screen_not_contains("Error");

    // Type some content
    harness.type_text("Hello, World!").unwrap();
    assert_eq!(harness.get_buffer_content().unwrap(), "Hello, World!");

    // Save the file with Ctrl+S
    harness
        .send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Should show "Saved" message
    harness.assert_screen_contains("Saved");

    // Verify file was created on disk with correct content
    assert!(new_file_path.exists());
    let saved_content = fs::read_to_string(&new_file_path).unwrap();
    assert_eq!(saved_content, "Hello, World!");
}

/// Test spawning CLI with non-existent file directly (via open_file)
#[test]
fn test_spawn_with_nonexistent_file() {
    use std::fs;
    use tempfile::TempDir;

    // Create a temporary directory
    let temp_dir = TempDir::new().unwrap();
    let new_file_path = temp_dir.path().join("spawn_test.rs");

    // Verify file doesn't exist
    assert!(!new_file_path.exists());

    let mut harness = EditorTestHarness::new(80, 24).unwrap();

    // Open non-existent file directly (simulating CLI spawn)
    harness.open_file(&new_file_path).unwrap();

    // Should show the filename
    harness.assert_screen_contains("spawn_test.rs");

    // Buffer should be empty
    assert_eq!(harness.get_buffer_content().unwrap(), "");

    // Type content and save
    harness.type_text("fn main() {}").unwrap();

    use crossterm::event::{KeyCode, KeyModifiers};
    harness
        .send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Verify file was created
    assert!(new_file_path.exists());
    let content = fs::read_to_string(&new_file_path).unwrap();
    assert_eq!(content, "fn main() {}");
}

/// Test Save As functionality
#[test]
fn test_save_as_functionality() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;
    use tempfile::TempDir;

    // Create a temporary directory and original file
    let temp_dir = TempDir::new().unwrap();
    let original_path = temp_dir.path().join("original.txt");
    fs::write(&original_path, "Original content").unwrap();

    let mut harness = EditorTestHarness::new(80, 24).unwrap();

    // Open the original file
    harness.open_file(&original_path).unwrap();
    harness.assert_screen_contains("original.txt");
    assert_eq!(harness.get_buffer_content().unwrap(), "Original content");

    // Trigger command palette with Ctrl+P
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Type to search for Save As command
    harness.type_text("Save File As").unwrap();

    // Confirm with Enter
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Should show the Save As prompt with current filename
    harness.assert_screen_contains("Save as:");

    // Clear the current filename and type new name
    // First select all with Ctrl+A
    harness
        .send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
        .unwrap();

    // Type new filename (relative path)
    let new_file_path = temp_dir.path().join("saved_as.txt");
    let new_path_str = new_file_path.to_str().unwrap();
    harness.type_text(new_path_str).unwrap();

    // Confirm with Enter
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Note: "Saved as:" status message may be overwritten by auto-revert status
    // We verify the save succeeded by checking the file exists and has correct content below

    // Verify new file was created with correct content
    assert!(new_file_path.exists());
    let new_content = fs::read_to_string(&new_file_path).unwrap();
    assert_eq!(new_content, "Original content");

    // Original file should still exist
    assert!(original_path.exists());

    // Buffer should now show the new filename
    harness.assert_screen_contains("saved_as.txt");
}

/// Test Save As with relative path
#[test]
fn test_save_as_relative_path() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;

    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();
    let project_dir = harness.project_dir().unwrap();

    // Create and open original file
    let original_path = project_dir.join("original.txt");
    fs::write(&original_path, "Test content").unwrap();
    harness.open_file(&original_path).unwrap();

    // Trigger command palette
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.type_text("Save File As").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Clear and type relative path
    harness
        .send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
        .unwrap();
    harness.type_text("relative_save.txt").unwrap();

    // Confirm
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Should save to working directory
    let expected_path = project_dir.join("relative_save.txt");
    assert!(
        expected_path.exists(),
        "File should be created at {:?}",
        expected_path
    );

    let content = fs::read_to_string(&expected_path).unwrap();
    assert_eq!(content, "Test content");
}

/// Test Save As creates parent directories if needed
#[test]
fn test_save_as_nested_path() {
    use crossterm::event::{KeyCode, KeyModifiers};

    let mut harness = EditorTestHarness::with_temp_project(80, 24).unwrap();
    let project_dir = harness.project_dir().unwrap();

    // Start with new buffer
    harness.new_buffer().unwrap();

    // Type some content
    harness.type_text("Nested file content").unwrap();

    // Trigger Save As via command palette
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.type_text("Save File As").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Type nested path (parent dir doesn't exist yet)
    let nested_path = project_dir.join("subdir").join("nested.txt");
    let nested_path_str = nested_path.to_str().unwrap();
    harness.type_text(nested_path_str).unwrap();

    // Confirm
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Note: This test verifies the error case since we don't auto-create parent dirs
    // The file won't be created because the parent directory doesn't exist
    // This documents current behavior - if we want to auto-create dirs, update this test
    if !nested_path.exists() {
        harness.assert_screen_contains("Error saving file");
    }
}

/// Test that long paths are truncated in the Open File prompt
///
/// When the path + input would exceed 90% of the prompt width, the path should be
/// truncated to show: /first/[...]/last/components/
#[test]
#[cfg_attr(windows, ignore)] // Path truncation format differs on Windows
fn test_open_file_prompt_truncates_long_paths() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;

    // Create a deeply nested directory structure to get a long path
    let temp_dir = tempfile::TempDir::new().unwrap();
    let mut nested_path = temp_dir.path().to_path_buf();

    // Create a path that's definitely longer than 80 chars
    // e.g., /tmp/.../very/deeply/nested/directory/structure/here
    for name in &[
        "very_long_directory_name",
        "another_long_name",
        "deeply",
        "nested",
        "path",
        "structure",
    ] {
        nested_path = nested_path.join(name);
    }
    fs::create_dir_all(&nested_path).unwrap();

    // Create a test file in the nested directory
    fs::write(nested_path.join("test.txt"), "test content").unwrap();

    // Create harness with the deeply nested working directory
    let mut harness =
        EditorTestHarness::with_config_and_working_dir(80, 24, Default::default(), nested_path)
            .unwrap();

    // Trigger Open File with Ctrl+O
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // The prompt should show "Open:" and the truncated path
    harness.assert_screen_contains("Open:");

    // The path should be truncated with "[...]" indicator
    // Since the path is very long, it should show something like:
    // Open: /tmp/[...]/path/structure/
    let screen = harness.screen_to_string();

    // Verify the path is truncated (contains [...])
    assert!(
        screen.contains("[...]"),
        "Long path should be truncated with [...]. Screen:\n{}",
        screen
    );

    // The test.txt file should still be visible in the file browser
    // (wait for directory to load)
    harness.sleep(std::time::Duration::from_millis(100));
    let _ = harness.editor_mut().process_async_messages();
    harness.render().unwrap();

    harness.assert_screen_contains("test.txt");
}

/// Test that Open File prompt shows completions popup immediately when opened (issue #193)
///
/// BUG: The suggestions dropdown/popup doesn't appear until the user types a few characters.
/// Users expect to see file completions immediately when the Open File prompt appears.
#[test]
fn test_open_file_prompt_shows_completions_immediately() {
    use crossterm::event::{KeyCode, KeyModifiers};
    use std::fs;
    use std::time::Duration;

    // Create a temp directory with test files directly in root
    let temp_dir = tempfile::TempDir::new().unwrap();
    let project_root = temp_dir.path().to_path_buf();

    // Create test files in root (these names won't appear elsewhere on screen)
    fs::write(project_root.join("alpha.txt"), "alpha content").unwrap();
    fs::write(project_root.join("beta.txt"), "beta content").unwrap();
    fs::write(project_root.join("gamma.txt"), "gamma content").unwrap();

    // Copy the real path_complete.ts plugin to the temp directory
    let real_plugins_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("plugins");
    let temp_plugins_dir = project_root.join("plugins");
    fs::create_dir(&temp_plugins_dir).unwrap();

    // Copy path_complete.ts
    fs::copy(
        real_plugins_dir.join("path_complete.ts"),
        temp_plugins_dir.join("path_complete.ts"),
    )
    .unwrap();

    // Copy the lib/ directory that path_complete.ts might depend on
    let real_lib_dir = real_plugins_dir.join("lib");
    if real_lib_dir.exists() {
        let temp_lib_dir = temp_plugins_dir.join("lib");
        fs::create_dir(&temp_lib_dir).unwrap();
        for entry in fs::read_dir(&real_lib_dir).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            if path.is_file() {
                fs::copy(&path, temp_lib_dir.join(path.file_name().unwrap())).unwrap();
            }
        }
    }

    // Create harness with temp directory
    let mut harness = EditorTestHarness::with_config_and_working_dir(
        80,
        24,
        Default::default(),
        project_root.clone(),
    )
    .unwrap();

    // Let plugins load
    harness.render().unwrap();
    for _ in 0..10 {
        let _ = harness.editor_mut().process_async_messages();
        harness.sleep(Duration::from_millis(20));
    }
    harness.render().unwrap();

    // Trigger Open File with Ctrl+O (no file opened first, so prompt starts empty)
    harness
        .send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
        .unwrap();

    harness.assert_screen_contains("Open:");

    // ISSUE #193: File completions should appear IMMEDIATELY when the prompt opens
    // The prompt starts empty, so we should see files from cwd right away.
    harness
        .wait_until(|h| {
            let screen = h.screen_to_string();
            // Should see test files in completions (these only appear in suggestions)
            screen.contains("alpha.txt")
                || screen.contains("beta.txt")
                || screen.contains("gamma.txt")
        })
        .expect("Completions should appear immediately when Open File prompt opens");

    let screen = harness.screen_to_string();
    println!("Screen after opening prompt:\n{}", screen);
}
