#include "gtest/gtest.h"
#include "lumacs/command_system.hpp"
#include "lumacs/editor_core.hpp"
#include "lumacs/minibuffer_manager.hpp" // For interactive commands
#include <string>
#include <vector>
#include <memory>
#include <algorithm> // For std::find

using namespace lumacs;

// Test fixture for CommandSystem tests
class CommandSystemTest : public ::testing::Test {
protected:
    EditorCore core; 
    CommandSystem* command_system; // Raw pointer to manager owned by core

    void SetUp() override {
        // EditorCore's constructor performs full initialization.
        // We get references to the managers owned by 'core'.
        command_system = &core.command_system(); 
        
        // Ensure initial clean state by clearing all commands registered by init.lua (if any)
        command_system->clear_commands();

        // Clear buffer and reset cursor
        if (core.buffer().line_count() > 0) {
            core.buffer().clear();
        }
        core.set_cursor({0,0});
    }

    void TearDown() override {
        // Nothing specific needed. core will clean up its managers.
    }
};

TEST_F(CommandSystemTest, RegisterAndExecuteBasicCommand) {
    bool command_executed = false;
    command_system->register_command("test-command", 
        [&command_executed](CommandContext& ctx) -> CommandResult {
            (void)ctx; // Mark ctx as unused
            command_executed = true;
            return {CommandStatus::Success, "Command executed"};
        }, 
        "A simple test command.");
    
    CommandResult result = command_system->execute("test-command", {});
    ASSERT_EQ(result.status, CommandStatus::Success);
    ASSERT_EQ(result.message, "Command executed");
    ASSERT_TRUE(command_executed);
}

TEST_F(CommandSystemTest, ExecuteNonExistentCommand) {
    CommandResult result = command_system->execute("non-existent-command", {});
    ASSERT_EQ(result.status, CommandStatus::Failure);
    ASSERT_EQ(result.message, "Unknown command or alias: non-existent-command");
}

TEST_F(CommandSystemTest, RegisterWithAliasAndExecute) {
    bool command_executed = false;
    command_system->register_command("aliased-command", 
        [&command_executed](CommandContext& ctx) -> CommandResult {
            (void)ctx; // Mark ctx as unused
            command_executed = true;
            return {CommandStatus::Success, "Aliased command executed"};
        }, 
        "A command with an alias.", false, "", {"alias1", "alias2"});
    
    CommandResult result = command_system->execute("alias1", {});
    ASSERT_EQ(result.status, CommandStatus::Success);
    ASSERT_EQ(result.message, "Aliased command executed");
    ASSERT_TRUE(command_executed);

    command_executed = false; // Reset for next execution
    result = command_system->execute("alias2", {});
    ASSERT_EQ(result.status, CommandStatus::Success);
    ASSERT_EQ(result.message, "Aliased command executed");
    ASSERT_TRUE(command_executed);

    // Should also work with canonical name
    command_executed = false;
    result = command_system->execute("aliased-command", {});
    ASSERT_EQ(result.status, CommandStatus::Success);
    ASSERT_EQ(result.message, "Aliased command executed");
    ASSERT_TRUE(command_executed);
}

TEST_F(CommandSystemTest, RegisterCommandOverwrite) {
    bool command_executed_v1 = false;
    command_system->register_command("overwrite-cmd", 
        [&command_executed_v1](CommandContext& ctx) -> CommandResult {
            (void)ctx;
            command_executed_v1 = true;
            return {CommandStatus::Success, "V1"};
        });
    
    // Overwrite with V2
    bool command_executed_v2 = false;
    command_system->register_command("overwrite-cmd", 
        [&command_executed_v2](CommandContext& ctx) -> CommandResult {
            (void)ctx;
            command_executed_v2 = true;
            return {CommandStatus::Success, "V2"};
        });

    CommandResult result = command_system->execute("overwrite-cmd", {});
    ASSERT_EQ(result.status, CommandStatus::Success);
    ASSERT_EQ(result.message, "V2");
    ASSERT_FALSE(command_executed_v1); // V1 should not have been called
    ASSERT_TRUE(command_executed_v2); // V2 should have been called
}

TEST_F(CommandSystemTest, AliasConflictResolution) {
    // Register a command that will be canonical
    command_system->register_command("master-cmd", 
        [](CommandContext& ctx) { 
            (void)ctx;
            return CommandResult{CommandStatus::Success, ""}; 
        }, 
        "Master command doc.", false, "", {"alias-conflict"});

    // Try to register another command whose alias conflicts with an existing canonical command
    // Note: The current implementation logs a warning to cerr, which we don't capture here,
    // but we verify the state of command registration.
    command_system->register_command("other-cmd", 
        [](CommandContext& ctx) { 
             (void)ctx;
            return CommandResult{CommandStatus::Success, ""}; 
        }, 
        "Other command doc.", false, "", {"alias-conflict"}); // "alias-conflict" clashes with "master-cmd" alias

    std::vector<std::string> names = command_system->get_command_names();
    ASSERT_TRUE(std::find(names.begin(), names.end(), "master-cmd") != names.end());
    ASSERT_TRUE(std::find(names.begin(), names.end(), "other-cmd") != names.end());
    ASSERT_TRUE(std::find(names.begin(), names.end(), "alias-conflict") != names.end()); 
}

TEST_F(CommandSystemTest, GetCommandNames) {
    command_system->register_command("cmd-a", 
        [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; }, 
        "Cmd A");
    command_system->register_command("cmd-b", 
        [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; }, 
        "Cmd B", false, "", {"alias-b"});
    
    std::vector<std::string> names = command_system->get_command_names();
    
    ASSERT_EQ(names.size(), 3); 
    ASSERT_TRUE(std::find(names.begin(), names.end(), "cmd-a") != names.end());
    ASSERT_TRUE(std::find(names.begin(), names.end(), "cmd-b") != names.end());
    ASSERT_TRUE(std::find(names.begin(), names.end(), "alias-b") != names.end());
}

TEST_F(CommandSystemTest, GetCommandDocString) {
    command_system->register_command("doc-cmd", 
        [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; }, 
        "This is documentation.");
    
    std::optional<std::string> doc = command_system->get_command_doc_string("doc-cmd");
    ASSERT_TRUE(doc.has_value());
    ASSERT_EQ(doc.value(), "This is documentation.");

    // For alias
    command_system->register_command("cmd-alias", 
        [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; }, 
        "Doc for alias", false, "", {"alias-for-doc"});
        
    doc = command_system->get_command_doc_string("alias-for-doc");
    ASSERT_TRUE(doc.has_value());
    ASSERT_EQ(doc.value(), "Doc for alias");

    // Non-existent
    doc = command_system->get_command_doc_string("non-existent");
    ASSERT_FALSE(doc.has_value());
}

// Tests for CommandContext argument accessors
TEST_F(CommandSystemTest, CommandContextGetStringArg) {
    // Use the fixture's core.minibuffer_manager()
    CommandContext ctx(core, core.minibuffer_manager(), {"arg1", "arg2"});

    ASSERT_EQ(ctx.get_string_arg(0).value(), "arg1");
    ASSERT_EQ(ctx.get_string_arg(1).value(), "arg2");
    ASSERT_FALSE(ctx.get_string_arg(2).has_value()); // Out of bounds
}

TEST_F(CommandSystemTest, CommandContextGetIntArg) {
    // Use the fixture's core.minibuffer_manager()
    CommandContext ctx(core, core.minibuffer_manager(), {"123", "-45", "not-a-number", "2147483648"}); // 2^31 for out of int range

    ASSERT_EQ(ctx.get_int_arg(0).value(), 123);
    ASSERT_EQ(ctx.get_int_arg(1).value(), -45);
    ASSERT_FALSE(ctx.get_int_arg(2).has_value()); // Invalid argument
    // Test for out of range (std::stoull can handle it, but stoi might throw)
    // std::stoi throws std::out_of_range for 2147483648
    ASSERT_FALSE(ctx.get_int_arg(3).has_value());
    ASSERT_FALSE(ctx.get_int_arg(4).has_value()); // Out of bounds

    // Ensure the message set by stoi failure is not propagating or crashing
}

TEST_F(CommandSystemTest, ExecuteInteractiveBasic) {
    // Register a simple interactive command that expects one string argument
    command_system->register_command("interactive-string-cmd", 
        [](CommandContext& ctx) -> CommandResult { 
            auto arg = ctx.get_string_arg(0);
            if (arg) {
                return {CommandStatus::Success, "Got string: " + arg.value()};
            }
            return {CommandStatus::Failure, "No argument provided"};
        }, 
        "An interactive command.", true, "s"); // 's' for string argument

    // Execute interactive command - should return PendingInput
    CommandResult result = command_system->execute_interactive("interactive-string-cmd");
    ASSERT_EQ(result.status, CommandStatus::PendingInput);
    ASSERT_EQ(result.message, "Waiting for user input.");

    // MinibufferManager should now be active and waiting for input
    ASSERT_TRUE(core.minibuffer_manager().is_active());
    ASSERT_EQ(core.minibuffer_manager().get_prompt(), "String: ");

    // Simulate input via MinibufferManager
    core.minibuffer_manager().handle_key_event("t");
    core.minibuffer_manager().handle_key_event("e");
    core.minibuffer_manager().handle_key_event("s");
    core.minibuffer_manager().handle_key_event("t");
    core.minibuffer_manager().handle_key_event("Return"); // Submit

    // After submission, the command should have executed
    ASSERT_FALSE(core.minibuffer_manager().is_active());
    
    // The result is communicated via side-effects (EditorCore message) since it's async
    ASSERT_EQ(core.last_message(), "Got string: test");
}

TEST_F(CommandSystemTest, ExecuteInteractiveCancel) {
    command_system->register_command("interactive-cancel-cmd", 
        [](CommandContext& ctx) -> CommandResult { 
            (void)ctx; // Mark ctx as unused
            return {CommandStatus::Success, "Should not be executed"};
        }, 
        "An interactive command that can be cancelled.", true, "s");

    CommandResult result = command_system->execute_interactive("interactive-cancel-cmd");
    ASSERT_EQ(result.status, CommandStatus::PendingInput);

    // Simulate cancel via MinibufferManager
    core.minibuffer_manager().handle_key_event("Escape"); // Cancel

    ASSERT_FALSE(core.minibuffer_manager().is_active());
    
    // We expect the command NOT to be executed
    ASSERT_NE(core.last_message(), "Should not be executed");
}
