#include <string.h>

#include <status.h>
#include <macros.h>
#include <constants.h>
#include <mongoose.h>
#include <logger.h>
#include <router.h>
#include <handlers.h>

#define HEADERS_FMT "Content-Type: %s"

// -2 for "%s" -1 for \0
#define HEADERS_FMT_LEN (sizeof(HEADERS_FMT) - 3)

static char*
add_extra_headers(char *extra_headers)
{
    char *result;
    size_t std_headers_len = strlen(global_config->http_server_opts.extra_headers);
    if(extra_headers == NULL)
    {
        result = malloc(sizeof(char) * (std_headers_len + 1));
        strcpy(result, global_config->http_server_opts.extra_headers);
        result[std_headers_len] = '\0';
        return result;
    }

    result = malloc(sizeof(char) * (std_headers_len + strlen(extra_headers) + 3));
    sprintf(result, "%s\r\n%s", global_config->http_server_opts.extra_headers, extra_headers);
    return result;
}

static void
send_response(struct mg_connection *nc, endpoint_response_t *response)
{
    if(response->status_code)
    {
        char *response_headers = malloc(sizeof(char) * (HEADERS_FMT_LEN + strlen(response->content_type) + 1));
        sprintf(response_headers, HEADERS_FMT, response->content_type);

        char *extra_headers = add_extra_headers(response_headers);
        mg_send_head(nc, response->status_code, response->content_length, extra_headers);
        mg_printf(nc, "%s", response->content);

        free(response_headers);
        free(extra_headers);
    }
}

static void
handle_websocket_request(struct mg_connection *nc, struct http_message *hm)
{
    struct mg_str method_websocket_str = mg_mk_str("WEBSOCKET");
    endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &method_websocket_str);

    if(!endpoint || !endpoint->func)
    {
        nc->flags |= MG_F_CLOSE_IMMEDIATELY;
    }
    else
    {
        endpoint->func(nc, hm, endpoint->args, NULL);

        for(int i = 0; i < endpoint->args_count; ++i)
        {
            if(endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
            {
                free((char*)endpoint->args[i].value.v_str);
            }
        }
    }
}

static void
handle_http_request(struct mg_connection *nc, struct http_message *hm)
{
    endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);

    endpoint_response_t response;
    response.content = NULL;
    response.alloced_content = false;

    M_RESPONSE_MSG(LOGGER_NONE, &response, 500, "server did not create a response");

    if(!endpoint)
    {
        /* Normalize path - resolve "." and ".." (in-place). */
        if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
            mg_http_send_error(nc, 400, global_config->http_server_opts.extra_headers);
            LOGGER_DEBUG("failed to normalize uri %.*s\n", hm->uri.len, hm->uri.p);
            endpoint_response_free_content(&response);
            return;
        }
        LOGGER_DEBUG("no endpoint found - serving file\n");

        char *request_file_org = malloc(sizeof(char) * hm->uri.len);
        strncpy(request_file_org, hm->uri.p + 1, hm->uri.len);
        request_file_org[hm->uri.len - 1] = '\0';

        char *request_file = request_file_org;
        while(request_file[0] == '/')
        {
            ++request_file;
        }

        char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config->content_dir) + 2));
        sprintf(request_file_path, "%s/%s", global_config->content_dir, request_file);
        int access_result = access(request_file_path, R_OK);
        free(request_file_path);
        free(request_file_org);
        if(access_result != -1)
        {
            response.status_code = 0;
            mg_serve_http(nc, hm, global_config->http_server_opts);
            LOGGER_DEBUG("serving %.*s\n", hm->uri.len, hm->uri.p);
            endpoint_response_free_content(&response);
            return;
        }

        LOGGER_DEBUG("serving 'not found'\n");
        endpoint = router_get_not_found_endpoint();
    }

    if(endpoint->method == HTTP_METHOD_OPTIONS)
    {
        LOGGER_DEBUG("sending options for %s\n", endpoint->full_route);
        char options_header[128];
        sprintf(options_header, "Allow: OPTIONS%s%s%s%s",
                endpoint->options & HTTP_METHOD_GET ? ", GET" : "",
                endpoint->options & HTTP_METHOD_POST ? ", POST" : "",
                endpoint->options & HTTP_METHOD_PUT ? ", PUT" : "",
                endpoint->options & HTTP_METHOD_DELETE ? ", DELETE" : ""
               );
        char *extra_headers = add_extra_headers(options_header);
        mg_send_head(nc, 204, 0, extra_headers);
        free(extra_headers);
    }
    else
    {
        LOGGER_DEBUG("calling endpoint function for route %s\n", endpoint->full_route);
        endpoint->func(nc, hm, endpoint->args, &response);
        char addr[32];
        mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP);
        LOGGER_DEBUG("sending response to %s\n", addr);
        send_response(nc, &response);
    }

    endpoint_response_free_content(&response);
    LOGGER_DEBUG("freeing endpoint args\n");
    for(int i = 0; i < endpoint->args_count; ++i)
    {
        if(endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
        {
            free((char*)endpoint->args[i].value.v_str);
        }
    }
}

void
handler_http(struct mg_connection *nc, int ev, void *p)
{
    if(ev == MG_EV_WEBSOCKET_HANDSHAKE_REQUEST)
    {
        struct http_message *hm = (struct http_message*)p;
        handle_websocket_request(nc, hm);
    }
    if(ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE)
    {
        status_send(nc);
    }
    if(ev == MG_EV_HTTP_REQUEST)
    {
        char addr[32];
        mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP);
        struct http_message *hm = (struct http_message*)p;
        LOGGER_DEBUG("======================================\n");
        LOGGER_DEBUG("new http %.*s request from %s for %.*s\n", hm->method.len, hm->method.p, addr, hm->uri.len, hm->uri.p);
        handle_http_request(nc, hm);
    }
}