#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "gpio_mock.hpp"
#include "../src/gpio_interface.hpp"
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>

// Test fixture for GPIO button tests
class GPIOButtonTestFixture {
public:
    std::shared_ptr<MockGPIOInterface> mock_gpio;
    std::vector<std::string> events;
    
    GPIOButtonTestFixture() {
        mock_gpio = std::make_shared<MockGPIOInterface>();
        events.clear();
    }
    
    void record_event(const std::string& event) {
        events.push_back(event);
    }
    
    std::unique_ptr<TestableGPIOButton> create_button(int pin, bool pull_up = true, 
                                                     bool active_state = false,
                                                     double bounce_time = 0.0,
                                                     double hold_time = 1.0,
                                                     bool hold_repeat = false) {
        // Use the shared mock directly
        return std::make_unique<TestableGPIOButton>(pin, pull_up, active_state,
                                                   bounce_time, hold_time, hold_repeat,
                                                   mock_gpio);
    }
};

TEST_CASE("GPIO Button Initialization", "[gpio][init]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Button initializes correctly") {
        auto button = fixture.create_button(23);
        REQUIRE_FALSE(button->is_running());
    }
    
    SECTION("Button starts successfully") {
        auto button = fixture.create_button(23);
        bool started = button->start();
        REQUIRE(started);
        REQUIRE(button->is_running());
        
        // Check that GPIO was set up correctly (gpiod-style)
        REQUIRE(fixture.mock_gpio->is_chip_open());
        REQUIRE(fixture.mock_gpio->is_pin_requested(23));
    }
}

TEST_CASE("GPIO Logic - Active Low Normally Open", "[gpio][logic][active-low][no]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Default configuration (pull_up=true, active_state=false)") {
        auto button = fixture.create_button(23, true, false);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        REQUIRE(fixture.events.empty());
        
        // Press button: GPIO goes LOW (pressed)
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Release button: GPIO goes HIGH (not pressed)
        fixture.events.clear();
        fixture.mock_gpio->set_pin_value(23, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "released");
    }
}

TEST_CASE("GPIO Logic - Active Low Normally Closed", "[gpio][logic][active-low][nc]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Normally closed configuration (pull_up=true, active_state=true)") {
        auto button = fixture.create_button(18, true, true);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(18, false); // LOW (not pressed) for normally closed
        
        REQUIRE(button->start());
        REQUIRE(fixture.events.empty());
        
        // Press button: GPIO goes HIGH (pressed) - button opens
        fixture.mock_gpio->set_pin_value(18, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Release button: GPIO goes LOW (not pressed) - button closes
        fixture.events.clear();
        fixture.mock_gpio->set_pin_value(18, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "released");
    }
}

TEST_CASE("GPIO Logic - Active High Normally Open", "[gpio][logic][active-high][no]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Active high configuration (pull_up=false, active_state=false)") {
        auto button = fixture.create_button(24, false, false);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(24, false); // LOW (not pressed) for pull-down
        
        REQUIRE(button->start());
        REQUIRE(fixture.events.empty());
        
        // Press button: GPIO goes HIGH (pressed)
        fixture.mock_gpio->set_pin_value(24, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Release button: GPIO goes LOW (not pressed)
        fixture.events.clear();
        fixture.mock_gpio->set_pin_value(24, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "released");
    }
}

TEST_CASE("GPIO Logic - Active High Normally Closed", "[gpio][logic][active-high][nc]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Active high normally closed (pull_up=false, active_state=true)") {
        auto button = fixture.create_button(19, false, true);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(19, true); // HIGH (not pressed) for this configuration
        
        REQUIRE(button->start());
        REQUIRE(fixture.events.empty());
        
        // Press button: GPIO goes LOW (pressed) - button disconnects from 3.3V
        fixture.mock_gpio->set_pin_value(19, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Release button: GPIO goes HIGH (not pressed) - button connects to 3.3V
        fixture.events.clear();
        fixture.mock_gpio->set_pin_value(19, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "released");
    }
}

TEST_CASE("Button Hold Detection", "[gpio][hold]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Hold event triggers after hold time") {
        auto button = fixture.create_button(23, true, false, 0.0, 0.5); // 500ms hold time
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        
        // Press button
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time but not enough for hold
        fixture.mock_gpio->advance_time(300);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Advance time to trigger hold
        fixture.mock_gpio->advance_time(300); // Total 600ms > 500ms hold time
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
        
        // Release button
        fixture.mock_gpio->set_pin_value(23, true);
        button->process_once();
        REQUIRE(fixture.events.back() == "released");
    }
    
    SECTION("Hold repeat works correctly") {
        auto button = fixture.create_button(23, true, false, 0.0, 0.2, true); // 200ms hold, repeat enabled
        
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        
        REQUIRE(button->start());
        
        // Press and hold button
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        
        // First hold trigger
        fixture.mock_gpio->advance_time(250);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events.back() == "held");
        
        // Second hold trigger (repeat)
        fixture.mock_gpio->advance_time(250);
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
    }
}

TEST_CASE("Hold Detection - All GPIO Configurations", "[gpio][hold][comprehensive]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Active Low Normally Closed - Hold detection") {
        auto button = fixture.create_button(18, true, true, 0.0, 0.4); // 400ms hold time
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: LOW (not pressed) for normally closed
        fixture.mock_gpio->set_pin_value(18, false);
        
        REQUIRE(button->start());
        
        // Press button: GPIO goes HIGH (pressed)
        fixture.mock_gpio->set_pin_value(18, true);
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time but not enough for hold
        fixture.mock_gpio->advance_time(200);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Advance time to trigger hold
        fixture.mock_gpio->advance_time(250); // Total 450ms > 400ms hold time
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
        
        // Release button: GPIO goes LOW (not pressed)
        fixture.mock_gpio->set_pin_value(18, false);
        button->process_once();
        REQUIRE(fixture.events.back() == "released");
    }
    
    SECTION("Active High Normally Open - Hold detection") {
        auto button = fixture.create_button(24, false, false, 0.0, 0.3); // 300ms hold time
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: LOW (not pressed) for pull-down
        fixture.mock_gpio->set_pin_value(24, false);
        
        REQUIRE(button->start());
        
        // Press button: GPIO goes HIGH (pressed)
        fixture.mock_gpio->set_pin_value(24, true);
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time but not enough for hold
        fixture.mock_gpio->advance_time(150);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Advance time to trigger hold
        fixture.mock_gpio->advance_time(200); // Total 350ms > 300ms hold time
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
        
        // Release button: GPIO goes LOW (not pressed)
        fixture.mock_gpio->set_pin_value(24, false);
        button->process_once();
        REQUIRE(fixture.events.back() == "released");
    }
    
    SECTION("Active High Normally Closed - Hold detection") {
        auto button = fixture.create_button(19, false, true, 0.0, 0.6); // 600ms hold time
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: HIGH (not pressed) for this configuration
        fixture.mock_gpio->set_pin_value(19, true);
        
        REQUIRE(button->start());
        
        // Press button: GPIO goes LOW (pressed)
        fixture.mock_gpio->set_pin_value(19, false);
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time but not enough for hold
        fixture.mock_gpio->advance_time(300);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Advance time to trigger hold
        fixture.mock_gpio->advance_time(350); // Total 650ms > 600ms hold time
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
        
        // Release button: GPIO goes HIGH (not pressed)
        fixture.mock_gpio->set_pin_value(19, true);
        button->process_once();
        REQUIRE(fixture.events.back() == "released");
    }
    
    SECTION("Hold repeat works for all configurations") {
        // Test hold repeat with Active High Normally Open
        auto button = fixture.create_button(24, false, false, 0.0, 0.15, true); // 150ms hold, repeat enabled
        
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        
        // Set initial state: LOW (not pressed) for pull-down
        fixture.mock_gpio->set_pin_value(24, false);
        
        REQUIRE(button->start());
        
        // Press and hold button
        fixture.mock_gpio->set_pin_value(24, true);
        button->process_once();
        
        // First hold trigger
        fixture.mock_gpio->advance_time(200);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events.back() == "held");
        
        // Second hold trigger (repeat)
        fixture.mock_gpio->advance_time(200);
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events.back() == "held");
        
        // Third hold trigger (repeat)
        fixture.mock_gpio->advance_time(200);
        button->process_once();
        REQUIRE(fixture.events.size() == 3);
        REQUIRE(fixture.events.back() == "held");
    }
}

TEST_CASE("Button Debouncing", "[gpio][debounce]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Debouncing prevents rapid fire events") {
        auto button = fixture.create_button(23, true, false, 0.05); // 50ms debounce
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        
        // Initial press
        fixture.mock_gpio->set_pin_value(23, false);
        fixture.mock_gpio->advance_time(60); // Advance past 50ms debounce time
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Rapid changes within debounce time should be ignored
        fixture.mock_gpio->advance_time(20); // 20ms < 50ms debounce
        fixture.mock_gpio->set_pin_value(23, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        fixture.mock_gpio->advance_time(20); // Total 40ms < 50ms
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Change after debounce time should be processed
        fixture.mock_gpio->advance_time(40); // Total 80ms > 50ms debounce
        fixture.mock_gpio->set_pin_value(23, true);
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
    }
}

TEST_CASE("Debouncing - All GPIO Configurations", "[gpio][debounce][comprehensive]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Active Low Normally Closed - Debouncing") {
        auto button = fixture.create_button(18, true, true, 0.06); // 60ms debounce
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: LOW (not pressed) for normally closed
        fixture.mock_gpio->set_pin_value(18, false);
        
        REQUIRE(button->start());
        
        // Initial press: GPIO goes HIGH
        fixture.mock_gpio->set_pin_value(18, true);
        fixture.mock_gpio->advance_time(70); // Advance past 60ms debounce time
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Rapid changes within debounce time should be ignored
        fixture.mock_gpio->advance_time(30); // 30ms < 60ms debounce
        fixture.mock_gpio->set_pin_value(18, false); // Try to release
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        fixture.mock_gpio->advance_time(20); // Total 50ms < 60ms
        fixture.mock_gpio->set_pin_value(18, true); // Try to press again
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Change after debounce time should be processed
        fixture.mock_gpio->advance_time(30); // Total 80ms > 60ms debounce
        fixture.mock_gpio->set_pin_value(18, false); // Release
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
    }
    
    SECTION("Active High Normally Open - Debouncing") {
        auto button = fixture.create_button(24, false, false, 0.04); // 40ms debounce
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: LOW (not pressed) for pull-down
        fixture.mock_gpio->set_pin_value(24, false);
        
        REQUIRE(button->start());
        
        // Initial press: GPIO goes HIGH
        fixture.mock_gpio->set_pin_value(24, true);
        fixture.mock_gpio->advance_time(50); // Advance past 40ms debounce time
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Rapid changes within debounce time should be ignored
        fixture.mock_gpio->advance_time(20); // 20ms < 40ms debounce
        fixture.mock_gpio->set_pin_value(24, false); // Try to release
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        fixture.mock_gpio->advance_time(15); // Total 35ms < 40ms
        fixture.mock_gpio->set_pin_value(24, true); // Try to press again
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Change after debounce time should be processed
        fixture.mock_gpio->advance_time(20); // Total 55ms > 40ms debounce
        fixture.mock_gpio->set_pin_value(24, false); // Release
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
    }
    
    SECTION("Active High Normally Closed - Debouncing") {
        auto button = fixture.create_button(19, false, true, 0.08); // 80ms debounce
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: HIGH (not pressed) for this configuration
        fixture.mock_gpio->set_pin_value(19, true);
        
        REQUIRE(button->start());
        
        // Initial press: GPIO goes LOW
        fixture.mock_gpio->set_pin_value(19, false);
        fixture.mock_gpio->advance_time(90); // Advance past 80ms debounce time
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Rapid changes within debounce time should be ignored
        fixture.mock_gpio->advance_time(40); // 40ms < 80ms debounce
        fixture.mock_gpio->set_pin_value(19, true); // Try to release
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        fixture.mock_gpio->advance_time(30); // Total 70ms < 80ms
        fixture.mock_gpio->set_pin_value(19, false); // Try to press again
        button->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // Change after debounce time should be processed
        fixture.mock_gpio->advance_time(20); // Total 90ms > 80ms debounce
        fixture.mock_gpio->set_pin_value(19, true); // Release
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
    }
    
    SECTION("Debouncing with different bounce times") {
        // Test very short debounce time (1ms) with Active Low Normally Open
        auto button1 = fixture.create_button(23, true, false, 0.001); // 1ms debounce
        
        button1->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button1->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: HIGH (not pressed) for pull-up
        fixture.mock_gpio->set_pin_value(23, true);
        
        REQUIRE(button1->start());
        
        // Press button
        fixture.mock_gpio->set_pin_value(23, false);
        fixture.mock_gpio->advance_time(2); // Advance past 1ms debounce time
        button1->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        // Very short bounce should still be debounced
        fixture.mock_gpio->set_pin_value(23, true);
        button1->process_once();
        REQUIRE(fixture.events.size() == 1); // Still just pressed
        
        // After 2ms, change should be processed
        fixture.mock_gpio->advance_time(2);
        button1->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
    }
    
    SECTION("Zero debounce time works correctly") {
        auto button = fixture.create_button(23, true, false, 0.0); // No debounce
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state: HIGH (not pressed) for pull-up
        fixture.mock_gpio->set_pin_value(23, true);
        
        REQUIRE(button->start());
        
        // Every change should be processed immediately
        fixture.mock_gpio->set_pin_value(23, false); // Press
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "pressed");
        
        fixture.mock_gpio->set_pin_value(23, true); // Release
        button->process_once();
        REQUIRE(fixture.events.size() == 2);
        REQUIRE(fixture.events[1] == "released");
        
        fixture.mock_gpio->set_pin_value(23, false); // Press again
        button->process_once();
        REQUIRE(fixture.events.size() == 3);
        REQUIRE(fixture.events[2] == "pressed");
    }
}

TEST_CASE("Mock GPIO Simulation", "[gpio][mock]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Mock can simulate button press sequence") {
        auto button = fixture.create_button(23);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        
        // Simulate a 100ms button press
        fixture.mock_gpio->simulate_button_press(23, 100, true);
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time to trigger release
        fixture.mock_gpio->advance_time(150);
        button->process_once();
        REQUIRE(fixture.events.back() == "released");
    }
    
    SECTION("Mock tracks GPIO operations") {
        auto button = fixture.create_button(23);
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        button->start();
        
        const auto& calls = fixture.mock_gpio->get_call_log();
        
        // Verify expected GPIO setup calls were made
        bool found_chip_open = false;
        bool found_line_request = false;
        
        for (const auto& call : calls) {
            if (call.find("open_gpio_chip(0)") != std::string::npos) found_chip_open = true;
            if (call.find("request_gpio_line(23") != std::string::npos) found_line_request = true;
        }
        
        REQUIRE(found_chip_open);
        REQUIRE(found_line_request);
    }
}

TEST_CASE("Edge Cases", "[gpio][edge-cases]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Button handles rapid start/stop cycles") {
        // Use different pins to avoid GPIO export conflicts
        // This tests button logic rather than hardware export behavior
        auto button1 = fixture.create_button(23);
        auto button2 = fixture.create_button(24);
        
        // Test first button
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE(button1->start());
        REQUIRE(button1->is_running());
        button1->stop();
        REQUIRE_FALSE(button1->is_running());
        
        // Test second button (different pin)
        fixture.mock_gpio->set_pin_value(24, true);
        REQUIRE(button2->start());
        REQUIRE(button2->is_running());
        button2->stop();
        REQUIRE_FALSE(button2->is_running());
    }
    
    SECTION("Button handles missing callbacks gracefully") {
        auto button = fixture.create_button(23);
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        
        // No callbacks set - should not crash
        fixture.mock_gpio->set_pin_value(23, false);
        REQUIRE_NOTHROW(button->process_once());
        
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE_NOTHROW(button->process_once());
    }
    
    SECTION("Zero hold time works correctly") {
        auto button = fixture.create_button(23, true, false, 0.0, 0.0); // Zero hold time
        
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        
        // Set initial state before starting button
        fixture.mock_gpio->set_pin_value(23, true); // HIGH (not pressed) for pull-up
        
        REQUIRE(button->start());
        
        // Press button
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        
        // Should trigger hold immediately
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        REQUIRE(fixture.events[0] == "held");
    }
}

TEST_CASE("GPIO Button - Negative Tests", "[gpio][negative]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Invalid GPIO pin numbers") {
        // Test with extreme pin numbers - should still create but may fail on start
        auto button_negative = fixture.create_button(-1);
        auto button_large = fixture.create_button(999999);
        
        // These should create successfully (validation happens at start)
        REQUIRE_FALSE(button_negative->is_running());
        REQUIRE_FALSE(button_large->is_running());
        
        // Set initial states
        fixture.mock_gpio->set_pin_value(-1, true);
        fixture.mock_gpio->set_pin_value(999999, true);
        
        // Starting should work with mock (real hardware would fail)
        REQUIRE(button_negative->start());
        REQUIRE(button_large->start());
        
        REQUIRE(button_negative->is_running());
        REQUIRE(button_large->is_running());
    }
    
    SECTION("Extreme timing values") {
        // Test with extreme timing values
        auto button_zero_times = fixture.create_button(23, true, false, 0.0, 0.0);
        auto button_huge_times = fixture.create_button(24, true, false, 999.0, 999.0);
        auto button_negative_times = fixture.create_button(25, true, false, -1.0, -1.0);
        
        fixture.mock_gpio->set_pin_value(23, true);
        fixture.mock_gpio->set_pin_value(24, true);
        fixture.mock_gpio->set_pin_value(25, true);
        
        // All should start successfully
        REQUIRE(button_zero_times->start());
        REQUIRE(button_huge_times->start());
        REQUIRE(button_negative_times->start());
        
        // Zero times should work normally
        button_zero_times->set_when_held([&fixture]() { fixture.record_event("held"); });
        fixture.mock_gpio->set_pin_value(23, false);
        button_zero_times->process_once();
        button_zero_times->process_once(); // Should trigger hold immediately
        REQUIRE(fixture.events.size() >= 1);
        REQUIRE(fixture.events.back() == "held");
    }
    
    SECTION("Missing callbacks - no crash") {
        auto button = fixture.create_button(23);
        
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE(button->start());
        
        // No callbacks set - should not crash on state changes
        fixture.mock_gpio->set_pin_value(23, false); // Press
        REQUIRE_NOTHROW(button->process_once());
        
        fixture.mock_gpio->set_pin_value(23, true); // Release
        REQUIRE_NOTHROW(button->process_once());
        
        // Advance time for hold
        fixture.mock_gpio->set_pin_value(23, false); // Press again
        REQUIRE_NOTHROW(button->process_once());
        fixture.mock_gpio->advance_time(2000); // Long hold
        REQUIRE_NOTHROW(button->process_once());
    }
    
    SECTION("Rapid start/stop cycles") {
        // Use different pins to avoid GPIO export conflicts
        // This tests button resilience rather than hardware export behavior
        for (int i = 0; i < 10; i++) {  // Reduced iterations, use different pins
            int pin = 15 + i;
            auto button = fixture.create_button(pin);
            
            fixture.mock_gpio->set_pin_value(pin, true);
            
            REQUIRE(button->start());
            REQUIRE(button->is_running());
            button->stop();
            REQUIRE_FALSE(button->is_running());
        }
    }
    
    SECTION("Multiple process_once calls without state changes") {
        auto button = fixture.create_button(23);
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_released([&fixture]() { fixture.record_event("released"); });
        
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE(button->start());
        
        // Multiple process calls without state changes should not generate events
        for (int i = 0; i < 50; i++) {
            button->process_once();
        }
        REQUIRE(fixture.events.empty());
        
        // Single state change should generate exactly one event
        fixture.mock_gpio->set_pin_value(23, false);
        button->process_once();
        REQUIRE(fixture.events.size() == 1);
        
        // More process calls without state changes
        for (int i = 0; i < 50; i++) {
            button->process_once();
        }
        REQUIRE(fixture.events.size() == 1); // Still just one event
    }
}

TEST_CASE("Mock GPIO - Error Simulation", "[gpio][mock][negative]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Mock handles invalid operations gracefully") {
        // Test operations on non-existent pins
        REQUIRE_FALSE(fixture.mock_gpio->get_pin_value(999));
        
        // Test operations before requesting line
        REQUIRE_FALSE(fixture.mock_gpio->is_pin_requested(123)); // Pin not requested yet
        
        // These should not crash
        REQUIRE_NOTHROW(fixture.mock_gpio->set_pin_value(999, true));
        REQUIRE_NOTHROW(fixture.mock_gpio->advance_time(1000));
    }
    
    SECTION("Mock time manipulation edge cases") {
        auto button = fixture.create_button(23, true, false, 0.1, 0.5); // 100ms debounce, 500ms hold
        
        button->set_when_pressed([&fixture]() { fixture.record_event("pressed"); });
        button->set_when_held([&fixture]() { fixture.record_event("held"); });
        
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE(button->start());
        
        // Press button
        fixture.mock_gpio->set_pin_value(23, false);
        fixture.mock_gpio->advance_time(110); // Advance past 100ms debounce time
        button->process_once();
        REQUIRE(fixture.events.back() == "pressed");
        
        // Advance time by huge amount
        fixture.mock_gpio->advance_time(999999999); // ~11 days
        button->process_once();
        
        // Should still work (hold should have triggered long ago)
        REQUIRE(fixture.events.size() >= 2);
        REQUIRE(fixture.events.back() == "held");
    }
    
    SECTION("Mock event queue stress test") {
        // Create many scheduled events
        for (int i = 0; i < 1000; i++) {
            fixture.mock_gpio->simulate_button_press(23, 10, true);
            fixture.mock_gpio->advance_time(1);
        }
        
        // Should handle large event queues without issues
        REQUIRE_NOTHROW(fixture.mock_gpio->advance_time(20000));
    }
    
    SECTION("Mock call log stress test") {
        auto button = fixture.create_button(23);
        
        fixture.mock_gpio->set_pin_value(23, true);
        button->start();
        
        // Generate many GPIO operations
        for (int i = 0; i < 1000; i++) {
            fixture.mock_gpio->set_pin_value(23, i % 2 == 0);
            button->process_once();
        }
        
        // Call log should track all operations
        const auto& calls = fixture.mock_gpio->get_call_log();
        REQUIRE(calls.size() > 1000); // Should have many logged calls
    }
}

TEST_CASE("Raspberry Pi 5 Hardware Validation", "[gpio][hardware][rpi5]") {
    GPIOButtonTestFixture fixture;
    
    SECTION("Pin number validation") {
        // Enable strict hardware validation
        fixture.mock_gpio->enable_strict_pin_validation();
        
        // Test invalid pins (should fail export)
        auto button_invalid_low = fixture.create_button(0);   // GPIO 0 reserved
        auto button_invalid_low2 = fixture.create_button(1);  // GPIO 1 reserved
        auto button_invalid_high = fixture.create_button(28); // GPIO 28+ don't exist
        auto button_negative = fixture.create_button(-5);     // Negative pin
        
        // These should fail to start with strict validation
        REQUIRE_FALSE(button_invalid_low->start());
        REQUIRE_FALSE(button_invalid_low2->start());
        REQUIRE_FALSE(button_invalid_high->start());
        REQUIRE_FALSE(button_negative->start());
        
        // Test valid pins (should succeed)
        auto button_valid_low = fixture.create_button(2);   // First valid GPIO
        auto button_valid_mid = fixture.create_button(18);  // Middle range
        auto button_valid_high = fixture.create_button(27); // Last valid GPIO
        
        fixture.mock_gpio->set_pin_value(2, true);
        fixture.mock_gpio->set_pin_value(18, true);
        fixture.mock_gpio->set_pin_value(27, true);
        
        REQUIRE(button_valid_low->start());
        REQUIRE(button_valid_mid->start());
        REQUIRE(button_valid_high->start());
    }
    
    SECTION("Hardware pull-up behavior") {
        // Test I2C pins with hardware pull-ups
        auto button_sda = fixture.create_button(2);  // GPIO 2 (SDA1)
        auto button_scl = fixture.create_button(3);  // GPIO 3 (SCL1)
        auto button_normal = fixture.create_button(18); // Normal GPIO
        
        // Start buttons to initialize GPIO pins with hardware defaults
        REQUIRE(button_sda->start());
        REQUIRE(button_scl->start());
        
        // Set normal GPIO pin manually since it doesn't have hardware pull-up
        fixture.mock_gpio->set_pin_value(18, false);  // Normal GPIO defaults to LOW
        REQUIRE(button_normal->start());
        
        // I2C pins should default to HIGH due to pull-ups (set during export)
        REQUIRE(fixture.mock_gpio->get_pin_value(2) == true);  // Hardware pull-up
        REQUIRE(fixture.mock_gpio->get_pin_value(3) == true);  // Hardware pull-up
        REQUIRE(fixture.mock_gpio->get_pin_value(18) == false); // Normal GPIO defaults to LOW
        
        // Verify hardware information
        REQUIRE(fixture.mock_gpio->pin_has_hardware_pullup(2) == true);
        REQUIRE(fixture.mock_gpio->pin_has_hardware_pullup(3) == true);
        REQUIRE(fixture.mock_gpio->pin_has_hardware_pullup(18) == false);
    }
    
    SECTION("Pin function groups") {
        // Test hardware pin groupings
        auto i2c_pins = fixture.mock_gpio->get_i2c_pins();
        auto spi_pins = fixture.mock_gpio->get_spi_pins();
        auto uart_pins = fixture.mock_gpio->get_uart_pins();
        auto valid_pins = fixture.mock_gpio->get_valid_gpio_pins();
        
        // Verify I2C pins
        REQUIRE(i2c_pins.size() == 2);
        REQUIRE(std::find(i2c_pins.begin(), i2c_pins.end(), 2) != i2c_pins.end());
        REQUIRE(std::find(i2c_pins.begin(), i2c_pins.end(), 3) != i2c_pins.end());
        
        // Verify SPI pins
        REQUIRE(spi_pins.size() == 5);
        REQUIRE(std::find(spi_pins.begin(), spi_pins.end(), 7) != spi_pins.end());   // CE1
        REQUIRE(std::find(spi_pins.begin(), spi_pins.end(), 8) != spi_pins.end());   // CE0
        REQUIRE(std::find(spi_pins.begin(), spi_pins.end(), 9) != spi_pins.end());   // MISO
        REQUIRE(std::find(spi_pins.begin(), spi_pins.end(), 10) != spi_pins.end());  // MOSI
        REQUIRE(std::find(spi_pins.begin(), spi_pins.end(), 11) != spi_pins.end());  // SCLK
        
        // Verify UART pins
        REQUIRE(uart_pins.size() == 2);
        REQUIRE(std::find(uart_pins.begin(), uart_pins.end(), 14) != uart_pins.end()); // TXD
        REQUIRE(std::find(uart_pins.begin(), uart_pins.end(), 15) != uart_pins.end()); // RXD
        
        // Verify valid pin range (GPIO 2-27)
        REQUIRE(valid_pins.size() == 26);
        REQUIRE(valid_pins.front() == 2);
        REQUIRE(valid_pins.back() == 27);
        
        // Verify all pins are valid
        for (int pin : valid_pins) {
            REQUIRE(fixture.mock_gpio->is_pin_valid(pin) == true);
        }
        
        // Verify invalid pins
        REQUIRE(fixture.mock_gpio->is_pin_valid(0) == false);
        REQUIRE(fixture.mock_gpio->is_pin_valid(1) == false);
        REQUIRE(fixture.mock_gpio->is_pin_valid(28) == false);
        REQUIRE(fixture.mock_gpio->is_pin_valid(-1) == false);
    }
    
    SECTION("Hardware error simulation") {
        // Test error simulation with direct GPIO operations
        fixture.mock_gpio->disable_strict_pin_validation();
        
        // Enable error simulation with 20% probability
        fixture.mock_gpio->enable_hardware_error_simulation(0.2);
        
        // Test gpiod-style operations directly (bypassing button logic)
        REQUIRE(fixture.mock_gpio->open_gpio_chip(0));
        
        int export_successes = 0;
        int export_failures = 0;
        
        for (int i = 0; i < 100; i++) {
            int pin = 20 + (i % 5);  // Use pins 20-24
            if (fixture.mock_gpio->request_gpio_line(pin, true)) {
                export_successes++;
                fixture.mock_gpio->release_gpio_line(pin);
            } else {
                export_failures++;
            }
        }
        
        // With error simulation, we should have some failures
        REQUIRE(export_failures > 0);
        REQUIRE(export_successes > 0);
        REQUIRE(export_successes + export_failures == 100);
        
        // Disable error simulation
        fixture.mock_gpio->disable_hardware_error_simulation();
        
        // Clean up any remaining requested pins
        for (int i = 0; i < 5; i++) {
            int pin = 20 + i;
            fixture.mock_gpio->release_gpio_line(pin);  // Clean up (ignore return value)
        }
        
        // Now all operations should succeed
        for (int i = 0; i < 5; i++) {  // Use different pins to avoid conflicts
            int pin = 15 + i;  // Use pins 15-19
            REQUIRE(fixture.mock_gpio->request_gpio_line(pin, true));
            fixture.mock_gpio->release_gpio_line(pin);
        }
        
        fixture.mock_gpio->close_gpio_chip();
    }
    
    SECTION("GPIO state validation") {
        fixture.mock_gpio->enable_strict_pin_validation();
        auto button = fixture.create_button(23);
        
        fixture.mock_gpio->set_pin_value(23, true);
        REQUIRE(button->start());
        
        // Test that pin is properly requested (gpiod-style)
        REQUIRE(fixture.mock_gpio->is_chip_open());
        REQUIRE(fixture.mock_gpio->is_pin_requested(23) == true);
        
        button->stop();
        
        // After stop, pin should be unexported
        // Note: Current implementation doesn't unexport on stop, but this tests the mock
    }
}
