#include "../librevna_minimal_driver.hpp"

#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include "librevna_protocol_common.hpp"

namespace radar::drivers {
namespace detail = radar::drivers::librevna::detail;
namespace {

[[nodiscard]] auto libusb_error_message(const std::string& prefix, int status_code) -> std::string {
    return prefix + ": " + libusb_error_name(status_code);
}

[[nodiscard]] auto is_supported_vid_pid(std::uint16_t vendor_id, std::uint16_t product_id) -> bool {
    return std::any_of(
        detail::kSupportedUsbIds.begin(),
        detail::kSupportedUsbIds.end(),
        [vendor_id, product_id](const auto& pair) {
            return pair.first == vendor_id && pair.second == product_id;
        }
    );
}

[[nodiscard]] auto parse_serial_string(libusb_device_handle* handle, std::uint8_t serial_index) -> std::string {
    if (handle == nullptr || serial_index == 0U) {
        return {};
    }

    std::array<unsigned char, 256> serial_bytes{};
    const auto length = libusb_get_string_descriptor_ascii(
        handle,
        serial_index,
        serial_bytes.data(),
        static_cast<int>(serial_bytes.size())
    );
    if (length <= 0) {
        return {};
    }

    return std::string(
        reinterpret_cast<const char*>(serial_bytes.data()),
        reinterpret_cast<const char*>(serial_bytes.data() + length)
    );
}

[[nodiscard]] auto find_matching_device_handle(
    libusb_context* usb_context,
    const std::string& expected_serial
) -> libusb_device_handle* {
    libusb_device** devices = nullptr;
    const auto device_count = libusb_get_device_list(usb_context, &devices);
    if (device_count < 0) {
        throw std::runtime_error(
            libusb_error_message("Failed to enumerate USB devices", static_cast<int>(device_count))
        );
    }

    libusb_device_handle* selected_handle = nullptr;
    for (ssize_t index = 0; index < device_count; ++index) {
        auto* device = devices[index];
        if (device == nullptr) {
            continue;
        }

        libusb_device_descriptor descriptor{};
        const auto descriptor_status = libusb_get_device_descriptor(device, &descriptor);
        if (descriptor_status != LIBUSB_SUCCESS) {
            continue;
        }
        if (!is_supported_vid_pid(descriptor.idVendor, descriptor.idProduct)) {
            continue;
        }

        libusb_device_handle* candidate_handle = nullptr;
        if (libusb_open(device, &candidate_handle) != LIBUSB_SUCCESS || candidate_handle == nullptr) {
            continue;
        }

        if (!expected_serial.empty()) {
            const auto serial = parse_serial_string(candidate_handle, descriptor.iSerialNumber);
            if (serial != expected_serial) {
                libusb_close(candidate_handle);
                continue;
            }
        }

        selected_handle = candidate_handle;
        break;
    }

    libusb_free_device_list(devices, 1);
    return selected_handle;
}

}  // namespace

void LibreVnaMinimalDriver::open_native() {
    if (usb_context_ != nullptr || usb_handle_ != nullptr) {
        throw std::runtime_error("LibreVNA native state is already initialized");
    }

    const auto init_status = libusb_init(&usb_context_);
    if (init_status != LIBUSB_SUCCESS) {
        usb_context_ = nullptr;
        throw std::runtime_error(libusb_error_message("Failed to initialize libusb", init_status));
    }

    try {
        auto* selected_handle = find_matching_device_handle(usb_context_, settings_.serial);

        if (selected_handle == nullptr) {
            const auto serial_hint = settings_.serial.empty() ? std::string() : " for serial '" + settings_.serial + "'";
            throw std::runtime_error("No compatible LibreVNA USB device found" + serial_hint);
        }

        usb_handle_ = selected_handle;

        const auto auto_detach_status = libusb_set_auto_detach_kernel_driver(usb_handle_, 1);
        if (auto_detach_status != LIBUSB_SUCCESS && auto_detach_status != LIBUSB_ERROR_NOT_SUPPORTED) {
            throw std::runtime_error(
                libusb_error_message("Failed to configure USB auto-detach kernel driver", auto_detach_status)
            );
        }

        const auto claim_status = libusb_claim_interface(usb_handle_, detail::kUsbInterface);
        if (claim_status != LIBUSB_SUCCESS) {
            throw std::runtime_error(libusb_error_message("Failed to claim LibreVNA USB interface", claim_status));
        }
        interface_claimed_ = true;

        rx_buffer_.clear();
        packet_queue_.clear();

        send_packet_no_payload(detail::kPacketRequestDeviceInfo, true);
        const auto info_packet = wait_for_packet(
            detail::kPacketDeviceInfo,
            std::chrono::steady_clock::now() + std::chrono::seconds(detail::kDeviceInfoTimeoutSeconds)
        );
        validate_device_info_payload(info_packet.payload);
        protocol_version_ = detail::read_u16_le(info_packet.payload, detail::kDeviceInfoProtocolOffset);
        device_num_ports_ = info_packet.payload[detail::kDeviceInfoNumPortsOffset];
    } catch (...) {
        close_native();
        throw;
    }
}

void LibreVnaMinimalDriver::close_native() {
    rx_buffer_.clear();
    packet_queue_.clear();

    if (usb_handle_ != nullptr && interface_claimed_) {
        libusb_release_interface(usb_handle_, detail::kUsbInterface);
        interface_claimed_ = false;
    }

    if (usb_handle_ != nullptr) {
        libusb_close(usb_handle_);
        usb_handle_ = nullptr;
    }

    if (usb_context_ != nullptr) {
        libusb_exit(usb_context_);
        usb_context_ = nullptr;
    }

    protocol_version_ = 0;
    device_num_ports_ = 0;
}

void LibreVnaMinimalDriver::send_packet(
    std::uint8_t packet_type,
    std::span<const std::uint8_t> payload,
    bool require_ack
) {
    if (usb_handle_ == nullptr) {
        throw std::runtime_error("Cannot send packet: LibreVNA USB handle is not open");
    }

    auto frame = encode_frame(packet_type, payload);

    int transferred = 0;
    const auto transfer_status = libusb_bulk_transfer(
        usb_handle_,
        detail::kEndpointOut,
        reinterpret_cast<unsigned char*>(frame.data()),
        static_cast<int>(frame.size()),
        &transferred,
        detail::kUsbWriteTimeoutMs
    );
    if (transfer_status != LIBUSB_SUCCESS) {
        throw std::runtime_error(libusb_error_message("Failed to write LibreVNA USB bulk packet", transfer_status));
    }
    if (transferred != static_cast<int>(frame.size())) {
        throw std::runtime_error("Incomplete LibreVNA USB bulk write");
    }

    if (require_ack) {
        wait_for_ack(std::chrono::steady_clock::now() + std::chrono::milliseconds(detail::kAckTimeoutMs));
    }
}

void LibreVnaMinimalDriver::send_packet_no_payload(std::uint8_t packet_type, bool require_ack) {
    send_packet(packet_type, std::span<const std::uint8_t>{}, require_ack);
}

void LibreVnaMinimalDriver::wait_for_ack(std::chrono::steady_clock::time_point deadline) {
    while (std::chrono::steady_clock::now() < deadline) {
        std::optional<NativePacket> ack_or_nack{};
        for (auto iter = packet_queue_.begin(); iter != packet_queue_.end(); ++iter) {
            if (iter->packet_type != detail::kPacketAck && iter->packet_type != detail::kPacketNack) {
                continue;
            }
            ack_or_nack = std::move(*iter);
            packet_queue_.erase(iter);
            break;
        }

        if (ack_or_nack.has_value()) {
            if (ack_or_nack->packet_type == detail::kPacketAck) {
                return;
            }
            throw std::runtime_error("LibreVNA returned NACK");
        }

        pump_usb(deadline);
    }

    throw std::runtime_error("Timeout waiting for LibreVNA ACK");
}

auto LibreVnaMinimalDriver::wait_for_packet(
    std::uint8_t expected_type,
    std::chrono::steady_clock::time_point deadline
) -> NativePacket {
    while (std::chrono::steady_clock::now() < deadline) {
        if (auto packet = pop_packet(expected_type); packet.has_value()) {
            return *packet;
        }
        pump_usb(deadline);
    }

    throw std::runtime_error("Timeout waiting for expected LibreVNA packet type");
}

void LibreVnaMinimalDriver::pump_usb(std::chrono::steady_clock::time_point deadline) {
    if (usb_handle_ == nullptr) {
        throw std::runtime_error("Cannot read packets: LibreVNA USB handle is not open");
    }

    const auto now = std::chrono::steady_clock::now();
    if (now >= deadline) {
        return;
    }

    const auto remaining_ms = std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now).count();
    const auto timeout_ms = static_cast<unsigned int>(
        std::clamp<std::int64_t>(remaining_ms, detail::kUsbReadPollMinTimeoutMs, detail::kUsbReadPollMaxTimeoutMs)
    );

    std::array<unsigned char, detail::kUsbReadChunkBytes> buffer{};
    int transferred = 0;
    const auto transfer_status = libusb_bulk_transfer(
        usb_handle_,
        detail::kEndpointIn,
        buffer.data(),
        static_cast<int>(buffer.size()),
        &transferred,
        timeout_ms
    );

    if (transfer_status == LIBUSB_ERROR_TIMEOUT) {
        return;
    }
    if (transfer_status != LIBUSB_SUCCESS) {
        throw std::runtime_error(libusb_error_message("Failed to read LibreVNA USB bulk packet", transfer_status));
    }
    if (transferred <= 0) {
        return;
    }

    rx_buffer_.insert(rx_buffer_.end(), buffer.begin(), buffer.begin() + transferred);
    decode_frames_from_buffer();
}

void LibreVnaMinimalDriver::decode_frames_from_buffer() {
    while (true) {
        const auto header_iter = std::find(rx_buffer_.begin(), rx_buffer_.end(), detail::kFrameHeader);
        if (header_iter == rx_buffer_.end()) {
            rx_buffer_.clear();
            return;
        }

        if (header_iter != rx_buffer_.begin()) {
            rx_buffer_.erase(rx_buffer_.begin(), header_iter);
        }

        if (rx_buffer_.size() < 4U) {
            return;
        }

        const auto declared_length = detail::read_u16_le(rx_buffer_, 1);
        if (declared_length < detail::kFrameOverheadBytes || declared_length > detail::kMaxFrameLengthBytes) {
            rx_buffer_.erase(rx_buffer_.begin());
            continue;
        }

        if (rx_buffer_.size() < declared_length) {
            return;
        }

        const std::vector<std::uint8_t> frame(rx_buffer_.begin(), rx_buffer_.begin() + declared_length);
        rx_buffer_.erase(rx_buffer_.begin(), rx_buffer_.begin() + declared_length);

        const auto packet_type = frame[3];
        const auto received_crc = detail::read_u32_le(frame, frame.size() - 4U);

        if (packet_type == detail::kPacketVnaDatapoint) {
            if (received_crc != 0U) {
                throw std::runtime_error("Invalid VNADatapoint CRC (expected zero)");
            }
        } else {
            const auto expected_crc = crc32(std::span<const std::uint8_t>(frame.data(), frame.size() - 4U));
            if (received_crc != expected_crc) {
                throw std::runtime_error("Invalid LibreVNA packet CRC");
            }
        }

        NativePacket packet{};
        packet.packet_type = packet_type;
        packet.payload.assign(frame.begin() + 4, frame.end() - 4);
        packet_queue_.push_back(std::move(packet));
    }
}

auto LibreVnaMinimalDriver::pop_packet(std::uint8_t packet_type) -> std::optional<NativePacket> {
    for (auto iter = packet_queue_.begin(); iter != packet_queue_.end(); ++iter) {
        if (iter->packet_type != packet_type) {
            continue;
        }

        auto packet = std::move(*iter);
        packet_queue_.erase(iter);
        return packet;
    }

    return std::nullopt;
}

}  // namespace radar::drivers
