local get_req_ssl, get_req_ssl_ctx
local get_socket_ssl, get_socket_ssl_ctx

local pok, nginx_c = pcall(require, "resty.openssl.auxiliary.nginx_c")

if pok and not os.getenv("CI_SKIP_NGINX_C") then
  get_req_ssl = nginx_c.get_req_ssl
  get_req_ssl_ctx = nginx_c.get_req_ssl_ctx
  get_socket_ssl = nginx_c.get_socket_ssl
  get_socket_ssl_ctx = nginx_c.get_socket_ssl
else
  local ffi = require "ffi"

  ffi.cdef [[
    // Nginx seems to always config _FILE_OFFSET_BITS=64, this should always be 8 byte
    typedef long long off_t;
    typedef unsigned int socklen_t; // windows uses int, same size
    typedef unsigned short in_port_t;

    typedef struct ssl_st SSL;
    typedef struct ssl_ctx_st SSL_CTX;

    typedef long (*ngx_recv_pt)(void *c, void *buf, size_t size);
    typedef long (*ngx_recv_chain_pt)(void *c, void *in,
        off_t limit);
    typedef long (*ngx_send_pt)(void *c, void *buf, size_t size);
    typedef void *(*ngx_send_chain_pt)(void *c, void *in,
        off_t limit);

    typedef struct {
      size_t             len;
      void               *data;
    } ngx_str_t;

    typedef struct {
      SSL             *connection;
      SSL_CTX         *session_ctx;
      // trimmed
    } ngx_ssl_connection_s;
  ]]

  local ngx_version = ngx.config.nginx_version
  local ngx_configure = ngx.config.nginx_configure()
  local ngx_has_http_v3 = ngx_configure and ngx_configure:find("--with-http_v3_module", 1, true)
  -- https://github.com/nginx/nginx/blob/master/src/core/ngx_connection.h
  if ngx_version == 1017008 or ngx_version == 1019003 or ngx_version == 1019009 
    or ngx_version == 1021004
    or (not ngx_has_http_v3 and (ngx_version == 1025003 or ngx_version == 1027001)) then
    -- 1.17.8, 1.19.3, 1.19.9, 1.21.4, 1.25.3
    ffi.cdef [[
    typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;

    typedef struct {
      void               *data;
      void               *read;
      void               *write;

      int                 fd;

      ngx_recv_pt         recv;
      ngx_send_pt         send;
      ngx_recv_chain_pt   recv_chain;
      ngx_send_chain_pt   send_chain;

      void               *listening;

      off_t               sent;

      void               *log;

      void               *pool;

      int                 type;

      void                *sockaddr;
      socklen_t           socklen;
      ngx_str_t           addr_text;

      // https://github.com/nginx/nginx/commit/be932e81a1531a3ba032febad968fc2006c4fa48
      ngx_proxy_protocol_t  *proxy_protocol;

      ngx_ssl_connection_s  *ssl;
      // trimmed
    } ngx_connection_s;
  ]]
  elseif ngx_has_http_v3 and (ngx_version == 1025003 or ngx_version == 1027001) then
    -- 1.25.3
    ffi.cdef [[
    typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
    typedef struct ngx_quic_stream_s ngx_quic_stream_t;

    typedef struct {
      void               *data;
      void               *read;
      void               *write;

      int                 fd;

      ngx_recv_pt         recv;
      ngx_send_pt         send;
      ngx_recv_chain_pt   recv_chain;
      ngx_send_chain_pt   send_chain;

      void               *listening;

      off_t               sent;

      void               *log;

      void               *pool;

      int                 type;

      void                *sockaddr;
      socklen_t           socklen;
      ngx_str_t           addr_text;

      // https://github.com/nginx/nginx/commit/be932e81a1531a3ba032febad968fc2006c4fa48
      ngx_proxy_protocol_t  *proxy_protocol;

      // https://github.com/nginx/nginx/commit/b813b9ec358862a2a94868bc057420d6eca5c05d
      ngx_quic_stream_t     *quic;

      ngx_ssl_connection_s  *ssl;
      // trimmed
    } ngx_connection_s;
    ]]
  else
    error("resty.openssl.auxiliary.nginx development mode doesn't support Nginx version " .. ngx_version ..
          ", please compile nginx with lua-resty-openssl-aux-module or lua-kong-nginx-module.", 2)
  end

  ffi.cdef [[
    typedef struct {
        ngx_connection_s                     *connection;
        // trimmed
    } ngx_stream_lua_request_s;

    typedef struct {
      unsigned int                     signature;         /* "HTTP" */

      ngx_connection_s                 *connection;
      // trimmed
    } ngx_http_request_s;
  ]]

  local get_request
  do
    local ok, exdata = pcall(require, "thread.exdata")
    if ok and exdata then
      function get_request()
        local r = exdata()
        if r ~= nil then
            return r
        end
      end

    else
      local getfenv = getfenv

      function get_request()
        return getfenv(0).__ngx_req
      end
    end
  end

  local SOCKET_CTX_INDEX = 1

  local NO_C_MODULE_WARNING_MSG_SHOWN = false
  local NO_C_MODULE_WARNING_MSG = "note resty.openssl.auxiliary.nginx is using plain FFI " ..
                                  "and it's only intended to be used in development, " ..
                                  "consider using lua-resty-openssl-aux-module or " .. 
                                  "lua-kong-nginx-module in production."

  local function get_ngx_ssl_from_req()
    if not NO_C_MODULE_WARNING_MSG_SHOWN then
      ngx.log(ngx.WARN, NO_C_MODULE_WARNING_MSG)
      NO_C_MODULE_WARNING_MSG_SHOWN = true
    end

    local c = get_request()
    if ngx.config.subsystem == "stream" then
      c = ffi.cast("ngx_stream_lua_request_s*", c)
    else -- http
      c = ffi.cast("ngx_http_request_s*", c)
    end

    local ngx_ssl = c.connection.ssl
    if ngx_ssl == nil then
      return nil, "c.connection.ssl is nil"
    end
    return ngx_ssl
  end

  get_req_ssl = function()
    local ssl, err = get_ngx_ssl_from_req()
    if err then
      return nil, err
    end

    return ssl.connection
  end

  get_req_ssl_ctx = function()
    local ssl, err = get_ngx_ssl_from_req()
    if err then
      return nil, err
    end

    return ssl.session_ctx
  end

  -- https://github.com/openresty/stream-lua-nginx-module/blob/master/src/ngx_stream_lua_socket_tcp.h
  ffi.cdef[[
    typedef struct ngx_http_lua_socket_tcp_upstream_s
                  ngx_http_lua_socket_tcp_upstream_t;

    typedef struct {
      ngx_connection_s                *connection;
      // trimmed
    } ngx_peer_connection_s;

    typedef
      int (*ngx_http_lua_socket_tcp_retval_handler_masked)(void *r,
      void *u, void *L);

    typedef void (*ngx_http_lua_socket_tcp_upstream_handler_pt_masked)
      (void *r, void *u);


    typedef
        int (*ngx_stream_lua_socket_tcp_retval_handler)(void *r,
            void *u, void *L);

    typedef void (*ngx_stream_lua_socket_tcp_upstream_handler_pt)
        (void *r, void *u);

    typedef struct {
      ngx_stream_lua_socket_tcp_retval_handler            read_prepare_retvals;
      ngx_stream_lua_socket_tcp_retval_handler            write_prepare_retvals;
      ngx_stream_lua_socket_tcp_upstream_handler_pt       read_event_handler;
      ngx_stream_lua_socket_tcp_upstream_handler_pt       write_event_handler;

      void                    *socket_pool;

      void                    *conf;
      void                    *cleanup;
      void                    *request;

      ngx_peer_connection_s            peer;
      // trimmed
    } ngx_stream_lua_socket_tcp_upstream_s;
  ]]

  local ngx_lua_version = ngx.config and
        ngx.config.ngx_lua_version and
        ngx.config.ngx_lua_version

  if ngx_lua_version >= 10019 and ngx_lua_version <= 10028 then
    -- https://github.com/openresty/lua-nginx-module/blob/master/src/ngx_http_lua_socket_tcp.h
    ffi.cdef[[
      typedef struct {
        ngx_http_lua_socket_tcp_retval_handler_masked          read_prepare_retvals;
        ngx_http_lua_socket_tcp_retval_handler_masked          write_prepare_retvals;
        ngx_http_lua_socket_tcp_upstream_handler_pt_masked     read_event_handler;
        ngx_http_lua_socket_tcp_upstream_handler_pt_masked     write_event_handler;

        void                            *udata_queue; // 0.10.19

        void                            *socket_pool;

        void                            *conf;
        void                            *cleanup;
        void                            *request;
        ngx_peer_connection_s            peer;
        // trimmed
      } ngx_http_lua_socket_tcp_upstream_s;
    ]]
  elseif ngx_lua_version < 10019 then
    -- the struct doesn't seem to get changed a long time since birth
    ffi.cdef[[
      typedef struct {
        ngx_http_lua_socket_tcp_retval_handler_masked          read_prepare_retvals;
        ngx_http_lua_socket_tcp_retval_handler_masked          write_prepare_retvals;
        ngx_http_lua_socket_tcp_upstream_handler_pt_masked     read_event_handler;
        ngx_http_lua_socket_tcp_upstream_handler_pt_masked     write_event_handler;

        void                            *socket_pool;

        void                            *conf;
        void                            *cleanup;
        void                            *request;
        ngx_peer_connection_s            peer;
        // trimmed
      } ngx_http_lua_socket_tcp_upstream_s;
    ]]
  else
    error("resty.openssl.auxiliary.nginx doesn't support lua-nginx-module version " .. (ngx_lua_version or "nil"), 2)
  end

  local function get_ngx_ssl_from_socket_ctx(sock)
    if not NO_C_MODULE_WARNING_MSG_SHOWN then
      ngx.log(ngx.WARN, NO_C_MODULE_WARNING_MSG)
      NO_C_MODULE_WARNING_MSG_SHOWN = true
    end

    local u = sock[SOCKET_CTX_INDEX]
    if u == nil then
      return nil, "lua_socket_tcp_upstream_t not found"
    end

    if ngx.config.subsystem == "stream" then
      u = ffi.cast("ngx_stream_lua_socket_tcp_upstream_s*", u)
    else -- http
      u = ffi.cast("ngx_http_lua_socket_tcp_upstream_s*", u)
    end

    local p = u.peer
    if p == nil then
      return nil, "u.peer is nil"
    end

    local uc = p.connection
    if uc == nil then
      return nil, "u.peer.connection is nil"
    end

    local ngx_ssl = uc.ssl
    if ngx_ssl == nil then
      return nil, "u.peer.connection.ssl is nil"
    end
    return ngx_ssl
  end

  get_socket_ssl = function(sock)
    local ssl, err = get_ngx_ssl_from_socket_ctx(sock)
    if err then
      return nil, err
    end

    return ssl.connection
  end

  get_socket_ssl_ctx = function(sock)
    local ssl, err = get_ngx_ssl_from_socket_ctx(sock)
    if err then
      return nil, err
    end

    return ssl.session_ctx
  end

end


return {
  get_req_ssl = get_req_ssl,
  get_req_ssl_ctx = get_req_ssl_ctx,
  get_socket_ssl = get_socket_ssl,
  get_socket_ssl_ctx = get_socket_ssl_ctx,
}
