#include "gpio_interface.hpp"
#include <chrono>
#include <thread>
#include <memory>
#include <string>
#include <iostream>
#include <map>
#include <optional>
#if HAVE_GPIOD
#include <gpiod.hpp>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <fstream>

// SysfsGPIOInterface implementation
bool SysfsGPIOInterface::export_gpio(int pin) {
    std::ofstream export_file("/sys/class/gpio/export");
    if (export_file.is_open()) {
        export_file << pin;
        export_file.close();
        return true;
    }
    return false;
}

bool SysfsGPIOInterface::unexport_gpio(int pin) {
    std::ofstream unexport_file("/sys/class/gpio/unexport");
    if (unexport_file.is_open()) {
        unexport_file << pin;
        unexport_file.close();
        return true;
    }
    return false;
}

bool SysfsGPIOInterface::set_direction(int pin, const std::string& direction) {
    std::string direction_path = "/sys/class/gpio/gpio" + std::to_string(pin) + "/direction";
    std::ofstream direction_file(direction_path);
    if (direction_file.is_open()) {
        direction_file << direction;
        direction_file.close();
        return true;
    }
    return false;
}

bool SysfsGPIOInterface::set_edge(int pin, const std::string& edge) {
    std::string edge_path = "/sys/class/gpio/gpio" + std::to_string(pin) + "/edge";
    std::ofstream edge_file(edge_path);
    if (edge_file.is_open()) {
        edge_file << edge;
        edge_file.close();
        return true;
    }
    return false;
}

int SysfsGPIOInterface::open_value_file(int pin) {
    std::string value_path = "/sys/class/gpio/gpio" + std::to_string(pin) + "/value";
    return open(value_path.c_str(), O_RDONLY);
}

bool SysfsGPIOInterface::read_pin_value_fd(int fd) {
    if (fd < 0) return false;
    
    char value;
    lseek(fd, 0, SEEK_SET);
    if (read(fd, &value, 1) == 1) {
        return (value == '1');
    }
    return false;
}

void SysfsGPIOInterface::close_value_file(int fd) {
    if (fd >= 0) {
        close(fd);
    }
}

void SysfsGPIOInterface::sleep_ms(int milliseconds) {
    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}

uint64_t SysfsGPIOInterface::get_time_ms() {
    auto now = std::chrono::steady_clock::now();
    auto duration = now.time_since_epoch();
    return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}

// GpiodGPIOInterface implementation using libgpiod v2 C++ bindings
#if HAVE_GPIOD

struct GpiodGPIOInterface::Impl {
    std::optional<gpiod::chip> chip;
    std::optional<gpiod::line_request> request;
    int requested_pin = -1;
};

GpiodGPIOInterface::GpiodGPIOInterface() : impl_(std::make_unique<Impl>()) {}

GpiodGPIOInterface::~GpiodGPIOInterface() {
    close_gpio_chip();
}

GpiodGPIOInterface::GpiodGPIOInterface(GpiodGPIOInterface&&) noexcept = default;
GpiodGPIOInterface& GpiodGPIOInterface::operator=(GpiodGPIOInterface&&) noexcept = default;

bool GpiodGPIOInterface::open_gpio_chip(int chip_num) {
    if (impl_->chip) {
        return true; // Already open
    }

    std::string chip_path = "/dev/gpiochip" + std::to_string(chip_num);
    try {
        impl_->chip.emplace(chip_path);
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Failed to open GPIO chip " << chip_path << ": " << e.what() << "\n";
        return false;
    }
}

void GpiodGPIOInterface::close_gpio_chip() {
    if (impl_->request) {
        impl_->request->release();
        impl_->request.reset();
        impl_->requested_pin = -1;
    }
    if (impl_->chip) {
        impl_->chip->close();
        impl_->chip.reset();
    }
}

bool GpiodGPIOInterface::request_gpio_line(int pin, bool pull_up) {
    if (!impl_->chip) {
        return false;
    }

    try {
        gpiod::line_settings settings;
        settings.set_direction(gpiod::line::direction::INPUT)
                .set_bias(pull_up ? gpiod::line::bias::PULL_UP
                                  : gpiod::line::bias::PULL_DOWN);

        auto request = impl_->chip->prepare_request()
            .set_consumer("systemd-gpio")
            .add_line_settings(static_cast<unsigned int>(pin), settings)
            .do_request();

        impl_->request.emplace(std::move(request));
        impl_->requested_pin = pin;
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Failed to request GPIO line " << pin << ": " << e.what() << "\n";
        return false;
    }
}

void GpiodGPIOInterface::release_gpio_line(int pin) {
    if (impl_->request && impl_->requested_pin == pin) {
        impl_->request->release();
        impl_->request.reset();
        impl_->requested_pin = -1;
    }
}

bool GpiodGPIOInterface::read_pin_value(int pin) {
    if (!impl_->request || impl_->requested_pin != pin) {
        return false;
    }

    try {
        auto value = impl_->request->get_value(static_cast<unsigned int>(pin));
        return value == gpiod::line::value::ACTIVE;
    } catch (const std::exception& e) {
        std::cerr << "Failed to read GPIO pin " << pin << ": " << e.what() << "\n";
        return false;
    }
}

bool GpiodGPIOInterface::wait_for_edge(int pin, int timeout_ms) {
    if (!impl_->request || impl_->requested_pin != pin) {
        return false;
    }

    try {
        auto timeout = std::chrono::milliseconds(timeout_ms);
        return impl_->request->wait_edge_events(
            std::chrono::duration_cast<std::chrono::nanoseconds>(timeout));
    } catch (const std::exception& e) {
        std::cerr << "Failed to wait for edge on GPIO pin " << pin << ": " << e.what() << "\n";
        return false;
    }
}

void GpiodGPIOInterface::sleep_ms(int milliseconds) {
    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}

uint64_t GpiodGPIOInterface::get_time_ms() {
    auto now = std::chrono::steady_clock::now();
    auto duration = now.time_since_epoch();
    return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}

#else  // !HAVE_GPIOD

// Stub implementation for systems without libgpiod
struct GpiodGPIOInterface::Impl {};

GpiodGPIOInterface::GpiodGPIOInterface() : impl_(std::make_unique<Impl>()) {}
GpiodGPIOInterface::~GpiodGPIOInterface() = default;
GpiodGPIOInterface::GpiodGPIOInterface(GpiodGPIOInterface&&) noexcept = default;
GpiodGPIOInterface& GpiodGPIOInterface::operator=(GpiodGPIOInterface&&) noexcept = default;

bool GpiodGPIOInterface::open_gpio_chip(int chip_num) {
    std::cerr << "Warning: libgpiod not available, GPIO operations will fail\n";
    (void)chip_num;
    return false;
}

void GpiodGPIOInterface::close_gpio_chip() {}

bool GpiodGPIOInterface::request_gpio_line(int pin, bool pull_up) {
    (void)pin; (void)pull_up;
    return false;
}

void GpiodGPIOInterface::release_gpio_line(int pin) { (void)pin; }

bool GpiodGPIOInterface::read_pin_value(int pin) {
    (void)pin;
    return false;
}

bool GpiodGPIOInterface::wait_for_edge(int pin, int timeout_ms) {
    (void)pin; (void)timeout_ms;
    return false;
}

void GpiodGPIOInterface::sleep_ms(int milliseconds) {
    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}

uint64_t GpiodGPIOInterface::get_time_ms() {
    auto now = std::chrono::steady_clock::now();
    auto duration = now.time_since_epoch();
    return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}

#endif  // HAVE_GPIOD

// TestableGPIOButton implementation
TestableGPIOButton::TestableGPIOButton(int pin, bool pull_up, bool active_state,
                                     double bounce_time, double hold_time, bool hold_repeat,
                                     std::shared_ptr<IGPIOInterface> gpio_interface)
    : pin_(pin), pull_up_(pull_up), active_state_(active_state),
      bounce_time_(bounce_time), hold_time_(hold_time), hold_repeat_(hold_repeat),
      gpio_interface_(gpio_interface ? gpio_interface : std::make_shared<GpiodGPIOInterface>()),
      running_(false), gpio_chip_open_(false), last_state_(false), press_time_(0), 
      last_bounce_time_(0), hold_triggered_(false) {
}

TestableGPIOButton::~TestableGPIOButton() {
    stop();
    cleanup_gpio();
}

void TestableGPIOButton::set_when_pressed(std::function<void()> callback) {
    when_pressed_ = callback;
}

void TestableGPIOButton::set_when_released(std::function<void()> callback) {
    when_released_ = callback;
}

void TestableGPIOButton::set_when_held(std::function<void()> callback) {
    when_held_ = callback;
}

bool TestableGPIOButton::start() {
    if (!setup_gpio()) {
        return false;
    }
    
    running_ = true;
    last_state_ = read_pin_state();
    return true;
}

void TestableGPIOButton::stop() {
    running_ = false;
}

bool TestableGPIOButton::setup_gpio() {
    // Open the GPIO chip
    if (!gpio_interface_->open_gpio_chip(0)) {
        return false;
    }
    gpio_chip_open_ = true;
    
    // Request the GPIO line with appropriate pull-up/down
    if (!gpio_interface_->request_gpio_line(pin_, pull_up_)) {
        gpio_interface_->close_gpio_chip();
        gpio_chip_open_ = false;
        return false;
    }
    
    return true;
}

void TestableGPIOButton::cleanup_gpio() {
    if (gpio_chip_open_) {
        gpio_interface_->release_gpio_line(pin_);
        gpio_interface_->close_gpio_chip();
        gpio_chip_open_ = false;
    }
}

bool TestableGPIOButton::read_pin_state() {
    bool pin_high = gpio_interface_->read_pin_value(pin_);
    
    // Apply pull-up logic:
    // - pull_up=true: button pulls to ground, so !pin_high when pressed
    // - pull_up=false: button pulls to 3.3V, so pin_high when pressed
    bool logical_state = pull_up_ ? !pin_high : pin_high;
    
    // Apply active state logic:
    // - active_state=false: normal (pressed = true when logical_state is true)  
    // - active_state=true: inverted (pressed = true when logical_state is false)
    return active_state_ ? !logical_state : logical_state;
}

void TestableGPIOButton::process_once() {
    if (!running_) return;
    
    bool current_state = read_pin_state();
    uint64_t current_time = gpio_interface_->get_time_ms();
    
    // Debounce check
    if (bounce_time_ > 0) {
        double time_since_bounce = (current_time - last_bounce_time_) / 1000.0;
        if (time_since_bounce < bounce_time_) {
            return;
        }
    }
    
    if (current_state != last_state_) {
        last_bounce_time_ = current_time;
        last_state_ = current_state;
        
        if (current_state) {
            // Button pressed
            press_time_ = current_time;
            hold_triggered_ = false;
            if (when_pressed_) {
                when_pressed_();
            }
        } else {
            // Button released
            if (when_released_) {
                when_released_();
            }
        }
    }
    
    // Check for hold condition
    if (current_state && !hold_triggered_ && when_held_) {
        double hold_duration = (current_time - press_time_) / 1000.0;
        if (hold_duration >= hold_time_) {
            hold_triggered_ = true;
            when_held_();
            
            // Handle hold repeat
            if (hold_repeat_) {
                hold_triggered_ = false; // Allow repeated holds
            }
        }
    }
}

void TestableGPIOButton::monitor_loop() {
    while (running_) {
        process_once();
        gpio_interface_->sleep_ms(10);
    }
}
