#ifndef NOMINMAX
#define NOMINMAX 1
#endif

#include "core/control_ipc.hpp"

#include <algorithm>
#include <array>
#include <cstdint>
#include <limits>
#include <memory>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#include <sddl.h>
#endif

namespace smartcut::core {
namespace {

#ifdef _WIN32
std::wstring wide(std::string_view text) {
  if (text.empty()) return {};
  const auto size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(),
                                        static_cast<int>(text.size()), nullptr, 0);
  if (size <= 0) return {};
  std::wstring result(static_cast<std::size_t>(size), L'\0');
  if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(),
                          static_cast<int>(text.size()), result.data(), size) != size) return {};
  return result;
}

std::wstring pipe_path(std::string_view name) {
  auto result = wide(name);
  if (result.empty()) return {};
  constexpr std::wstring_view prefix = L"\\\\.\\pipe\\";
  if (!result.starts_with(prefix)) result = std::wstring(prefix) + result;
  return result;
}

class PipeSecurity {
 public:
  PipeSecurity() {
    HANDLE token = nullptr;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return;
    DWORD required = 0;
    GetTokenInformation(token, TokenUser, nullptr, 0, &required);
    std::vector<std::uint8_t> buffer(required);
    if (required == 0 || !GetTokenInformation(token, TokenUser, buffer.data(), required, &required)) {
      CloseHandle(token);
      return;
    }
    CloseHandle(token);
    const auto* user = reinterpret_cast<const TOKEN_USER*>(buffer.data());
    LPWSTR sid_text = nullptr;
    if (!ConvertSidToStringSidW(user->User.Sid, &sid_text)) return;
    const std::wstring sddl = std::wstring(L"D:P(A;;GA;;;SY)(A;;GA;;;") + sid_text + L")";
    LocalFree(sid_text);
    if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
            sddl.c_str(), SDDL_REVISION_1, &descriptor_, nullptr)) return;
    attributes_.nLength = sizeof(attributes_);
    attributes_.lpSecurityDescriptor = descriptor_;
    attributes_.bInheritHandle = FALSE;
  }

  ~PipeSecurity() { if (descriptor_) LocalFree(descriptor_); }
  PipeSecurity(const PipeSecurity&) = delete;
  PipeSecurity& operator=(const PipeSecurity&) = delete;
  SECURITY_ATTRIBUTES* get() { return descriptor_ ? &attributes_ : nullptr; }

 private:
  PSECURITY_DESCRIPTOR descriptor_{};
  SECURITY_ATTRIBUTES attributes_{};
};

bool overlapped_transfer(HANDLE pipe, bool writing, void* data, DWORD bytes,
                         DWORD& transferred, unsigned timeout_ms) {
  OVERLAPPED operation{};
  operation.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
  if (!operation.hEvent) return false;
  const BOOL started = writing
      ? WriteFile(pipe, data, bytes, &transferred, &operation)
      : ReadFile(pipe, data, bytes, &transferred, &operation);
  bool passed = started != FALSE;
  if (!passed && GetLastError() == ERROR_IO_PENDING) {
    const auto wait = WaitForSingleObject(operation.hEvent, timeout_ms);
    if (wait == WAIT_OBJECT_0) {
      passed = GetOverlappedResult(pipe, &operation, &transferred, FALSE) != FALSE;
    } else {
      CancelIoEx(pipe, &operation);
      WaitForSingleObject(operation.hEvent, INFINITE);
    }
  }
  CloseHandle(operation.hEvent);
  return passed;
}

bool connect_with_timeout(HANDLE pipe, unsigned timeout_ms) {
  OVERLAPPED operation{};
  operation.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
  if (!operation.hEvent) return false;
  const BOOL started = ConnectNamedPipe(pipe, &operation);
  bool connected = started != FALSE;
  if (!connected) {
    const auto error = GetLastError();
    if (error == ERROR_PIPE_CONNECTED) {
      connected = true;
    } else if (error == ERROR_IO_PENDING) {
      const auto wait = WaitForSingleObject(operation.hEvent, timeout_ms);
      DWORD transferred = 0;
      if (wait == WAIT_OBJECT_0) {
        connected = GetOverlappedResult(pipe, &operation, &transferred, FALSE) != FALSE;
      } else {
        CancelIoEx(pipe, &operation);
        WaitForSingleObject(operation.hEvent, INFINITE);
      }
    }
  }
  CloseHandle(operation.hEvent);
  return connected;
}

bool write_all(HANDLE pipe, const void* data, std::size_t bytes, unsigned timeout_ms) {
  const auto* cursor = static_cast<const std::uint8_t*>(data);
  while (bytes > 0) {
    const auto chunk = static_cast<DWORD>(std::min<std::size_t>(bytes, std::numeric_limits<DWORD>::max()));
    DWORD written = 0;
    if (!overlapped_transfer(pipe, true, const_cast<std::uint8_t*>(cursor), chunk, written, timeout_ms) ||
        written == 0) return false;
    cursor += written; bytes -= written;
  }
  return true;
}

bool read_all(HANDLE pipe, void* data, std::size_t bytes, unsigned timeout_ms) {
  auto* cursor = static_cast<std::uint8_t*>(data);
  while (bytes > 0) {
    const auto chunk = static_cast<DWORD>(std::min<std::size_t>(bytes, std::numeric_limits<DWORD>::max()));
    DWORD received = 0;
    if (!overlapped_transfer(pipe, false, cursor, chunk, received, timeout_ms) || received == 0) return false;
    cursor += received; bytes -= received;
  }
  return true;
}

bool write_frame(HANDLE pipe, std::string_view payload, unsigned timeout_ms) {
  if (payload.size() > control_ipc_max_message || payload.size() > std::numeric_limits<std::uint32_t>::max()) return false;
  const auto size = static_cast<std::uint32_t>(payload.size());
  const std::array<std::uint8_t, 4> header{static_cast<std::uint8_t>(size),
                                          static_cast<std::uint8_t>(size >> 8U),
                                          static_cast<std::uint8_t>(size >> 16U),
                                          static_cast<std::uint8_t>(size >> 24U)};
  return write_all(pipe, header.data(), header.size(), timeout_ms) &&
         write_all(pipe, payload.data(), payload.size(), timeout_ms);
}

std::optional<std::string> read_frame(HANDLE pipe, unsigned timeout_ms) {
  std::array<std::uint8_t, 4> header{};
  if (!read_all(pipe, header.data(), header.size(), timeout_ms)) return std::nullopt;
  const auto size = static_cast<std::uint32_t>(header[0]) |
                    (static_cast<std::uint32_t>(header[1]) << 8U) |
                    (static_cast<std::uint32_t>(header[2]) << 16U) |
                    (static_cast<std::uint32_t>(header[3]) << 24U);
  if (size > control_ipc_max_message) return std::nullopt;
  std::string payload(size, '\0');
  if (size > 0 && !read_all(pipe, payload.data(), payload.size(), timeout_ms)) return std::nullopt;
  return payload;
}
#endif

}  // namespace

int serve_control_pipe(std::string_view pipe_name, const ControlRequestHandler& handler,
                       std::size_t request_limit, unsigned connect_timeout_ms) {
#ifdef _WIN32
  const auto path = pipe_path(pipe_name);
  if (path.empty() || !handler) return 2;
  PipeSecurity security;
  if (!security.get()) return 3;
  std::size_t served = 0;
  while (request_limit == 0 || served < request_limit) {
    const HANDLE pipe = CreateNamedPipeW(path.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE |
        FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
        1, 64U * 1024U, 64U * 1024U, 0, security.get());
    if (pipe == INVALID_HANDLE_VALUE) return 3;
    const bool connected = connect_with_timeout(pipe, connect_timeout_ms);
    const auto request = connected ? read_frame(pipe, 5000) : std::nullopt;
    bool passed = request.has_value();
    if (request) {
      const auto response = handler(*request);
      passed = write_frame(pipe, response, 5000);
      FlushFileBuffers(pipe);
    }
    DisconnectNamedPipe(pipe); CloseHandle(pipe);
    if (!connected) return 5;
    if (!passed) return 4;
    ++served;
    if (request->find("\"command\":\"shutdown\"") != std::string::npos) break;
  }
  return 0;
#else
  (void)pipe_name; (void)handler; (void)request_limit; (void)connect_timeout_ms; return 2;
#endif
}

std::optional<std::string> request_control_pipe(std::string_view pipe_name,
                                                std::string_view request_json,
                                                unsigned timeout_ms) {
#ifdef _WIN32
  const auto path = pipe_path(pipe_name);
  if (path.empty() || request_json.size() > control_ipc_max_message) return std::nullopt;
  const auto deadline = GetTickCount64() + timeout_ms; HANDLE pipe = INVALID_HANDLE_VALUE;
  do {
    pipe = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
                       FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, nullptr);
    if (pipe != INVALID_HANDLE_VALUE) break;
    const auto error = GetLastError(); const auto now = GetTickCount64(); if (now >= deadline) break;
    const auto remaining = static_cast<DWORD>(std::min<ULONGLONG>(deadline - now, 100));
    if (error == ERROR_PIPE_BUSY) WaitNamedPipeW(path.c_str(), remaining); else if (error == ERROR_FILE_NOT_FOUND) Sleep(std::min<DWORD>(remaining, 10)); else break;
  } while (true);
  if (pipe == INVALID_HANDLE_VALUE) return std::nullopt;
  const bool sent = write_frame(pipe, request_json, timeout_ms);
  const auto response = sent ? read_frame(pipe, timeout_ms) : std::nullopt;
  CloseHandle(pipe);
  return response;
#else
  (void)pipe_name; (void)request_json; (void)timeout_ms; return std::nullopt;
#endif
}

}  // namespace smartcut::core
