use bkmr::cli::args::{Cli, Commands};
use bkmr::application::services::factory::{create_bookmark_service, create_action_service};
use bkmr::domain::repositories::repository::BookmarkRepository;
use bkmr::domain::tag::Tag;
use bkmr::infrastructure::repositories::sqlite::repository::SqliteBookmarkRepository;
use bkmr::util::testing::{init_test_env, EnvGuard};
use serial_test::serial;
use std::collections::HashSet;
use std::io::Write;
use tempfile::NamedTempFile;

fn create_test_repository() -> SqliteBookmarkRepository {
    let app_state = bkmr::app_state::AppState::read_global();
    let repository = SqliteBookmarkRepository::from_url(&app_state.settings.db_url)
        .expect("Could not create test repository");
    
    // Clean the database for test isolation
    repository.empty_bookmark_table()
        .expect("Could not empty bookmark table");
    
    repository
}

#[test]
#[serial]
fn given_stdin_content_when_add_command_with_stdin_flag_then_stores_content_in_url_column() {
    // Arrange
    let _env = init_test_env();
    let _guard = EnvGuard::new();
    
    let repository = create_test_repository();
    let bookmark_service = create_bookmark_service();
    
    // Create a temporary file to simulate stdin
    let mut temp_file = NamedTempFile::new().unwrap();
    let test_content = "echo 'Hello from stdin test!'";
    temp_file.write_all(test_content.as_bytes()).unwrap();
    temp_file.flush().unwrap();
    
    // Simulate the CLI command structure (not executed in this test)
    let _cli = Cli {
        name: None,
        config: None,
        debug: 0,
        openai: false,
        generate_config: false,
        command: Some(Commands::Add {
            url: None,
            tags: Some("test,stdin".to_string()),
            title: Some("test_stdin_command".to_string()),
            desc: None,
            no_web: true,
            edit: false,
            bookmark_type: "shell".to_string(),
            clone_id: None,
            stdin: true,
        }),
    };
    
    // Simulate stdin input by setting final_url directly in the test
    // We'll manually test the core logic by calling the bookmark service directly
    let final_content = test_content.to_string();
    
    // Create the expected tags
    let mut tag_set = HashSet::new();
    tag_set.insert(Tag::new("test").unwrap());
    tag_set.insert(Tag::new("stdin").unwrap());
    tag_set.insert(Tag::new("_shell_").unwrap());
    
    // Act - Add the bookmark with stdin content
    let bookmark = bookmark_service.add_bookmark(
        &final_content,
        Some("test_stdin_command"),
        None,
        Some(&tag_set),
        false,
    ).unwrap();
    
    // Assert
    assert!(bookmark.id.is_some(), "Bookmark should have an ID");
    assert_eq!(bookmark.url, test_content, "URL should contain the stdin content");
    assert_eq!(bookmark.title, "test_stdin_command", "Title should match");
    assert_eq!(bookmark.description, "", "Description should be empty");
    assert!(bookmark.tags.contains(&Tag::new("_shell_").unwrap()), "Should have shell tag");
    assert!(bookmark.tags.contains(&Tag::new("test").unwrap()), "Should have test tag");
    assert!(bookmark.tags.contains(&Tag::new("stdin").unwrap()), "Should have stdin tag");
    
    // Verify it's stored correctly in the database
    let stored_bookmark = repository.get_by_id(bookmark.id.unwrap()).unwrap().unwrap();
    assert_eq!(stored_bookmark.url, test_content, "Stored URL should contain the stdin content");
}

#[test]
#[serial]
fn given_shell_bookmark_when_open_command_with_no_edit_flag_then_executes_without_interaction() {
    // Arrange
    let _env = init_test_env();
    let _guard = EnvGuard::new();
    
    let repository = create_test_repository();
    let bookmark_service = create_bookmark_service();
    let action_service = create_action_service();
    
    // Create a shell bookmark
    let mut tag_set = HashSet::new();
    tag_set.insert(Tag::new("_shell_").unwrap());
    tag_set.insert(Tag::new("test").unwrap());
    
    let test_script = "echo 'Test script executed'";
    let bookmark = bookmark_service.add_bookmark(
        test_script,
        Some("test_no_edit"),
        None,
        Some(&tag_set),
        false,
    ).unwrap();
    
    let bookmark_id = bookmark.id.unwrap();
    
    // Simulate the CLI command structure with --no-edit flag (not executed in this test)
    let _cli = Cli {
        name: None,
        config: None,
        debug: 0,
        openai: false,
        generate_config: false,
        command: Some(Commands::Open {
            ids: bookmark_id.to_string(),
            no_edit: true,
            script_args: vec![],
        }),
    };
    
    // Act - Execute the open command with no_edit=true
    let stored_bookmark = bookmark_service.get_bookmark(bookmark_id).unwrap().unwrap();
    let result = action_service.execute_default_action_with_options(&stored_bookmark, true, &[]);
    
    // Assert
    assert!(result.is_ok(), "Should execute successfully with no-edit flag");
    
    // Verify access was recorded
    let updated_bookmark = repository.get_by_id(bookmark_id).unwrap().unwrap();
    assert_eq!(updated_bookmark.access_count, 1, "Access count should be incremented");
}

#[test]
#[serial]
fn given_non_shell_bookmark_when_open_command_with_no_edit_flag_then_executes_normally() {
    // Arrange
    let _env = init_test_env();
    let _guard = EnvGuard::new();
    
    let repository = create_test_repository();
    let bookmark_service = create_bookmark_service();
    let action_service = create_action_service();
    
    // Create a non-shell bookmark (URI)
    let tag_set = HashSet::new(); // No shell tag
    
    let test_url = "https://example.com";
    let bookmark = bookmark_service.add_bookmark(
        test_url,
        Some("test_url_no_edit"),
        None,
        Some(&tag_set),
        false,
    ).unwrap();
    
    let bookmark_id = bookmark.id.unwrap();
    
    // Act - Execute the open command with no_edit=true (should not affect non-shell bookmarks)
    let stored_bookmark = bookmark_service.get_bookmark(bookmark_id).unwrap().unwrap();
    let result = action_service.execute_default_action_with_options(&stored_bookmark, true, &[]);
    
    // Assert
    assert!(result.is_ok(), "Should execute successfully even for non-shell bookmarks");
    
    // Verify access was recorded
    let updated_bookmark = repository.get_by_id(bookmark_id).unwrap().unwrap();
    assert_eq!(updated_bookmark.access_count, 1, "Access count should be incremented");
}

#[test]
#[serial]
fn given_add_command_with_stdin_and_type_shell_when_executed_then_creates_shell_bookmark() {
    // Arrange
    let _env = init_test_env();
    let _guard = EnvGuard::new();
    
    let repository = create_test_repository();
    let bookmark_service = create_bookmark_service();
    
    let test_script = "#!/bin/bash\necho 'Complex shell script'\nls -la";
    let title = "complex_shell_script";
    
    // Create the expected tags for shell type
    let mut tag_set = HashSet::new();
    tag_set.insert(Tag::new("_shell_").unwrap());
    tag_set.insert(Tag::new("automation").unwrap());
    tag_set.insert(Tag::new("test").unwrap());
    
    // Act - Simulate adding with stdin (manually test the core logic)
    let bookmark = bookmark_service.add_bookmark(
        test_script,
        Some(title),
        None,
        Some(&tag_set),
        false,
    ).unwrap();
    
    // Assert
    assert!(bookmark.id.is_some(), "Bookmark should have an ID");
    assert_eq!(bookmark.url, test_script, "URL should contain the script content");
    assert_eq!(bookmark.title, title, "Title should match");
    assert_eq!(bookmark.description, "", "Description should be empty for stdin input");
    assert!(bookmark.tags.contains(&Tag::new("_shell_").unwrap()), "Should have shell tag");
    
    // Verify the bookmark type is correctly identified
    let stored_bookmark = repository.get_by_id(bookmark.id.unwrap()).unwrap().unwrap();
    assert_eq!(stored_bookmark.url, test_script, "Stored script should match input");
    assert!(stored_bookmark.tags.contains(&Tag::new("_shell_").unwrap()), "Should be tagged as shell");
}

#[test]
#[serial]
fn given_stdin_with_multiline_content_when_add_command_then_preserves_formatting() {
    // Arrange
    let _env = init_test_env();
    let _guard = EnvGuard::new();
    
    let repository = create_test_repository();
    let bookmark_service = create_bookmark_service();
    
    let multiline_content = "#!/bin/bash\n\n# This is a comment\necho 'Line 1'\necho 'Line 2'\n\n# Another comment\necho 'Line 3'";
    let title = "multiline_script";
    
    let mut tag_set = HashSet::new();
    tag_set.insert(Tag::new("_shell_").unwrap());
    tag_set.insert(Tag::new("multiline").unwrap());
    
    // Act
    let bookmark = bookmark_service.add_bookmark(
        multiline_content,
        Some(title),
        None,
        Some(&tag_set),
        false,
    ).unwrap();
    
    // Assert
    assert_eq!(bookmark.url, multiline_content, "Multiline content should be preserved exactly");
    
    // Verify in database
    let stored_bookmark = repository.get_by_id(bookmark.id.unwrap()).unwrap().unwrap();
    assert_eq!(stored_bookmark.url, multiline_content, "Stored multiline content should match");
    assert!(stored_bookmark.url.contains('\n'), "Should preserve newlines");
    assert!(stored_bookmark.url.contains("# This is a comment"), "Should preserve comments");
}