local skynet = require "skynet"
local socket = require "skynet.socket"
local httpd = require "http.httpd"
local sockethelper = require "http.sockethelper"
local websocket = require "http.websocket"
local urllib = require "http.url"

local util = require "swt.util"
local mimetypes = require "swt.mimetypes"
local json = require "json"

local function response(id, code, resp, content_type)
    if type(resp) == "table" then
        resp = json.encode(resp)
        content_type = "application/json"
    end

    local header = {
        ["Access-Control-Allow-Origin"] = "*",
        ["Access-Control-Allow-Methods"] = "POST,GET,OPTIONS",
        ["Access-Control-Allow-Headers"] = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With", -- luacheck: ignore
    }
    if content_type then
        header["Content-Type"] = content_type
    end

    local ok, err = httpd.write_response(sockethelper.writefunc(id), code, resp, header)
    if not ok then
        util.log_error("swt http response error fd = %d, %s", id, err)
    end
    socket.close(id)
end

local function upgrade(handle_agent, request)
    local ok, err = websocket.accept(request.id, handle_agent, "ws", request.addr, {
        upgrade = {
            header = request.headers,
            method = request.method,
            url = request.url,
        },
    })

    if not ok and handle_agent.error then
        handle_agent.error(request.id, err)
    end
end

local function dispatch(router, id, addr)
    local code, url, method, headers, body = httpd.read_request(sockethelper.readfunc(id), 8192)
    if not code then
        if url ~= sockethelper.socket_error then
            util.log_error("swt http read error: %s", url)
        end
        socket.close(id)
        return
    end

    if code ~= 200 then
        response(id, code)
        return
    end

    local path, query = urllib.parse(url)
    query = urllib.parse_query(query)

    local ok, error = router:execute(method, path, {
        id = id,
        addr = addr,
        path = path,
        method = method,
        headers = headers,
        query = query,
        body = body,
    })

    if not ok then
        response(id, 404, error)
        return
    end
end

local function static(path, options, request)
    local real_path = request.path
    if options and options.default and real_path == path then
        real_path = real_path .. "/" .. options.default
    end

    local now = skynet.now()
    local cache
    local expire = 0
    if options and options.cache then
        cache = options.cache[real_path]
        expire = options.expire or 30 * 100
    end

    if not cache or cache.expire < now then
        cache = {
            expire = now + expire,
            code = 200,
            resp = "",
        }
        local filename = string.sub(real_path, 2)
        local f = io.open(filename, "rb")
        if f then
            cache.resp = f:read("*a")
            cache.content_type = mimetypes.guess(filename or "")
            f:close()
        else
            cache.code = 404
        end
    end

    if options and options.cache then
        options.cache[real_path] = cache
    end

    response(request.id, cache.code, cache.resp, cache.content_type)
end

return {
    response = response,
    upgrade = upgrade,
    dispatch = dispatch,
    static = static,
}
