#pragma once

#include "../src/gpio_interface.hpp"
#include <map>
#include <queue>
#include <vector>
#include <chrono>
#include <string>
#include <memory>
#include <cstdlib>

// Mock GPIO interface for testing - closely models Raspberry Pi 5 hardware
class MockGPIOInterface : public IGPIOInterface {
private:
    struct GPIOState {
        bool requested = false;
        bool pull_up = true;
        bool value = false;
        bool has_hardware_pullup = false;
        bool has_hardware_pulldown = false;
    };
    
    std::map<int, GPIOState> gpio_states_;
    uint64_t current_time_ms_ = 0;
    bool chip_open_ = false;
    
    // Event simulation
    struct GPIOEvent {
        uint64_t time_ms;
        int pin;
        bool value;
    };
    std::queue<GPIOEvent> scheduled_events_;
    
    // Call tracking
    std::vector<std::string> call_log_;
    
    // Hardware simulation settings
    bool simulate_hardware_errors_ = false;
    double hardware_error_probability_ = 0.0;
    bool strict_pin_validation_ = false;  // Default to false for backward compatibility
    
    // Hardware-specific pin information for Raspberry Pi 5
    static bool is_valid_gpio_pin(int pin) {
        // Valid GPIO pins on Raspberry Pi 5 (BCM numbering): 2-27
        // GPIO 0,1 are reserved for system use
        // GPIO 28+ don't exist on the 40-pin header
        return (pin >= 2 && pin <= 27);
    }
    
    static bool has_hardware_pullup(int pin) {
        // GPIO 2 (SDA1) and GPIO 3 (SCL1) have fixed 1.8kΩ pull-ups for I2C
        return (pin == 2 || pin == 3);
    }
    
    static bool get_hardware_default_state(int pin) {
        // Pins with hardware pull-ups default to HIGH
        if (has_hardware_pullup(pin)) return true;
        // Most other pins default to LOW
        return false;
    }
    
    bool should_simulate_error() {
        if (!simulate_hardware_errors_) return false;
        return (static_cast<double>(rand()) / RAND_MAX) < hardware_error_probability_;
    }
    
public:
    // IGPIOInterface implementation (new gpiod-style API)
    bool open_gpio_chip(int chip_num = 0) override {
        call_log_.push_back("export_gpio(" + std::to_string(pin) + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        // Validate pin number (if strict validation enabled)
        if (strict_pin_validation_ && !is_valid_gpio_pin(pin)) {
            return false;  // Invalid pin number (would return EINVAL on real hardware)
        }
        
        // Check if already exported
        if (gpio_states_.find(pin) != gpio_states_.end() && gpio_states_[pin].exported) {
            return false;  // Already exported (would return EBUSY on real hardware)
        }
        
        // Check if pin already exists before creating the entry
        bool had_manual_value = (gpio_states_.find(pin) != gpio_states_.end());
        
        // Initialize GPIO state with hardware defaults
        GPIOState& state = gpio_states_[pin];
        
        state.exported = true;
        state.direction = "in";  // Default direction
        state.edge = "none";     // Default edge detection
        state.has_hardware_pullup = has_hardware_pullup(pin);
        state.has_hardware_pulldown = false;  // RPi 5 doesn't have fixed pull-downs
        
        // Only set hardware default if no manual value was set
        if (!had_manual_value) {
            state.value = get_hardware_default_state(pin);  // Hardware-specific default
        }
        // Otherwise preserve the manually set value
        
        state.fd = -1;  // No file descriptor yet
        
        return true;
    }
    
    bool unexport_gpio(int pin) override {
        call_log_.push_back("unexport_gpio(" + std::to_string(pin) + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        // Check if pin was exported
        auto it = gpio_states_.find(pin);
        if (it == gpio_states_.end() || !it->second.exported) {
            return false;  // Pin not exported (would return EINVAL on real hardware)
        }
        
        gpio_states_.erase(pin);
        return true;
    }
    
    bool set_direction(int pin, const std::string& direction) override {
        call_log_.push_back("set_direction(" + std::to_string(pin) + ", " + direction + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        // Check if pin is exported
        auto it = gpio_states_.find(pin);
        if (it == gpio_states_.end() || !it->second.exported) {
            return false;  // Pin not exported
        }
        
        // Validate direction value
        if (direction != "in" && direction != "out" && direction != "high" && direction != "low") {
            return false;  // Invalid direction (would return EINVAL on real hardware)
        }
        
        it->second.direction = direction;
        
        // Set initial value for output directions
        if (direction == "high") {
            it->second.value = true;
        } else if (direction == "low") {
            it->second.value = false;
        }
        
        return true;
    }
    
    bool set_edge(int pin, const std::string& edge) override {
        call_log_.push_back("set_edge(" + std::to_string(pin) + ", " + edge + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        // Check if pin is exported
        auto it = gpio_states_.find(pin);
        if (it == gpio_states_.end() || !it->second.exported) {
            return false;  // Pin not exported
        }
        
        // Validate edge value
        if (edge != "none" && edge != "rising" && edge != "falling" && edge != "both") {
            return false;  // Invalid edge (would return EINVAL on real hardware)
        }
        
        // Edge detection only works for input pins
        if (it->second.direction == "out") {
            return false;  // Can't set edge on output pin
        }
        
        it->second.edge = edge;
        return true;
    }
    
    int open_value_file(int pin) override {
        call_log_.push_back("open_value_file(" + std::to_string(pin) + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return -1;
        }
        
        // Check if pin is exported
        auto it = gpio_states_.find(pin);
        if (it == gpio_states_.end() || !it->second.exported) {
            return -1;  // Pin not exported (would return ENOENT on real hardware)
        }
        
        // Check if already has an open file descriptor
        if (it->second.fd != -1) {
            return it->second.fd;  // Return existing fd (real hardware allows multiple opens)
        }
        
        int fd = next_fd_++;
        it->second.fd = fd;
        return fd;
    }
    
    bool read_pin_value(int fd) override {
        call_log_.push_back("read_pin_value(" + std::to_string(fd) + ")");
        
        // Process any scheduled events that should have happened by now
        process_scheduled_events();
        
        // Find the pin associated with this fd
        for (auto& [pin, state] : gpio_states_) {
            if (state.fd == fd) {
                return state.value;
            }
        }
        return false;
    }
    
    void close_value_file(int fd) override {
        call_log_.push_back("close_value_file(" + std::to_string(fd) + ")");
        // Find and clear the fd
        for (auto& [pin, state] : gpio_states_) {
            if (state.fd == fd) {
                state.fd = -1;
                break;
            }
        }
    }
    
    void sleep_ms(int milliseconds) override {
        call_log_.push_back("sleep_ms(" + std::to_string(milliseconds) + ")");
        advance_time(milliseconds);
    }
    
    uint64_t get_time_ms() override {
        return current_time_ms_;
    }
    
    // Hardware simulation control methods
    void enable_hardware_error_simulation(double probability = 0.1) {
        simulate_hardware_errors_ = true;
        hardware_error_probability_ = probability;
    }
    
    void disable_hardware_error_simulation() {
        simulate_hardware_errors_ = false;
        hardware_error_probability_ = 0.0;
    }
    
    void enable_strict_pin_validation() {
        strict_pin_validation_ = true;
    }
    
    void disable_strict_pin_validation() {
        strict_pin_validation_ = false;
    }
    
    // Hardware information methods
    bool is_pin_valid(int pin) const {
        return is_valid_gpio_pin(pin);
    }
    
    bool pin_has_hardware_pullup(int pin) const {
        return has_hardware_pullup(pin);
    }
    
    std::vector<int> get_valid_gpio_pins() const {
        std::vector<int> pins;
        for (int pin = 2; pin <= 27; pin++) {
            pins.push_back(pin);
        }
        return pins;
    }
    
    std::vector<int> get_i2c_pins() const {
        return {2, 3};  // GPIO 2 (SDA1), GPIO 3 (SCL1)
    }
    
    std::vector<int> get_spi_pins() const {
        return {7, 8, 9, 10, 11};  // CE1, CE0, MISO, MOSI, SCLK
    }
    
    std::vector<int> get_uart_pins() const {
        return {14, 15};  // TXD, RXD
    }
    
    // Test helper methods
    void set_pin_value(int pin, bool value) {
        // Create pin state if it doesn't exist (for testing convenience)
        // This bypasses normal hardware validation for testing
        gpio_states_[pin].value = value;
        
        // If pin exists and has hardware pull-up, respect that unless explicitly overridden
        auto it = gpio_states_.find(pin);
        if (it != gpio_states_.end() && it->second.exported && it->second.has_hardware_pullup) {
            // Hardware pull-up pins resist being pulled low (but can be overridden for testing)
            gpio_states_[pin].value = value;
        }
    }
    
    bool get_pin_value(int pin) const {
        auto it = gpio_states_.find(pin);
        return (it != gpio_states_.end()) ? it->second.value : false;
    }
    
    void advance_time(uint64_t milliseconds) {
        current_time_ms_ += milliseconds;
        process_scheduled_events();
    }
    
    void schedule_pin_change(int pin, bool value, uint64_t delay_ms) {
        GPIOEvent event;
        event.time_ms = current_time_ms_ + delay_ms;
        event.pin = pin;
        event.value = value;
        scheduled_events_.push(event);
    }
    
    void reset() {
        gpio_states_.clear();
        current_time_ms_ = 0;
        next_fd_ = 100;
        while (!scheduled_events_.empty()) {
            scheduled_events_.pop();
        }
        call_log_.clear();
    }
    
    const std::vector<std::string>& get_call_log() const {
        return call_log_;
    }
    
    void clear_call_log() {
        call_log_.clear();
    }
    
    bool is_pin_exported(int pin) const {
        auto it = gpio_states_.find(pin);
        return (it != gpio_states_.end()) && it->second.exported;
    }
    
    std::string get_pin_direction(int pin) const {
        auto it = gpio_states_.find(pin);
        return (it != gpio_states_.end()) ? it->second.direction : "";
    }
    
    std::string get_pin_edge(int pin) const {
        auto it = gpio_states_.find(pin);
        return (it != gpio_states_.end()) ? it->second.edge : "";
    }
    
    // Simulate button press sequence
    void simulate_button_press(int pin, uint64_t press_duration_ms, bool pull_up = true) {
        // For pull-up configuration, pressing pulls the pin LOW
        bool pressed_value = pull_up ? false : true;
        bool released_value = !pressed_value;
        
        set_pin_value(pin, pressed_value);  // Press
        schedule_pin_change(pin, released_value, press_duration_ms);  // Release
    }
    
    // Simulate button hold sequence
    void simulate_button_hold(int pin, uint64_t hold_duration_ms, bool pull_up = true) {
        bool pressed_value = pull_up ? false : true;
        set_pin_value(pin, pressed_value);  // Press and hold
        // Don't schedule release - let the test control when to release
    }
    
private:
    void process_scheduled_events() {
        while (!scheduled_events_.empty()) {
            const auto& event = scheduled_events_.front();
            if (event.time_ms <= current_time_ms_) {
                set_pin_value(event.pin, event.value);
                scheduled_events_.pop();
            } else {
                break;
            }
        }
    }
};
