#include "transmission.h"
#include "program.h"
#include "buffer.h"
#include "../depends/cJSON.h"
#include "rss_html_common.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

/* TODO: Make port configurable */

int transmission_connect(TransmissionSession *session) {
    int result = 0;
    Buffer buffer;
    buffer_init(&buffer);

    const char *args[] = { "curl", "-s", "http://127.0.0.1:9091/transmission/rpc", NULL };
    result = program_exec(args, program_buffer_write_callback, &buffer);
    if(result != 0) {
        fprintf(stderr, "Failed to retrieve transmission session id\n");
        goto cleanup;
    }

    char *session_id_start = strstr(buffer.data, "X-Transmission-Session-Id: ");
    if(!session_id_start) {
        fprintf(stderr, "Failed to find session id in transmission response\n");
        result = -1;
        goto cleanup;
    }

    char *session_id_end = strchr(session_id_start + 27, '<');
    if(!session_id_end) {
        fprintf(stderr, "Failed to find session id in transmission response\n");
        result = -1;
        goto cleanup;
    }

    const size_t session_id_len = session_id_end - session_id_start;
    if(session_id_len + 1 > sizeof(session->session_header)) {
        fprintf(stderr, "Session id is too long\n");
        result = -1;
        goto cleanup;
    }

    memcpy(session->session_header, session_id_start, session_id_end - session_id_start);
    session->session_header[session_id_end - session_id_start] = '\0';

    cleanup:
    buffer_deinit(&buffer);
    return result;
}

int transmission_is_daemon_running(void) {
    const char *args[] = { "transmission-remote", "-si", NULL };
    return program_exec(args, NULL, NULL);
}

int transmission_start_daemon(const char *download_dir) {
    /* TODO: Make seed ratio configurable */
    const char *args[] = { "transmission-daemon", "--global-seedratio", "2.0", "--download-dir", download_dir, NULL };
    int res = program_exec(args, NULL, NULL);
    if(res != 0)
        return res;

    fprintf(stderr, "Waiting for the transmission daemon to startup...\n");
    int num_tries = 0;
    const int max_tries = 7; /* 7 seconds */

    while(transmission_is_daemon_running() != 0 && num_tries < max_tries) {
        sleep(1);
        ++num_tries;
    }
    
    if(num_tries == max_tries) {
        fprintf(stderr, "Failed to launch transmission daemon in 7 seconds\n");
        return -1;
    }

    fprintf(stderr, "The transmission daemon is now running!\n");
    return 0;
}

static int transmission_response_is_success(cJSON *json_root) {
    cJSON *result_field = cJSON_GetObjectItemCaseSensitive(json_root, "result");
    if(!result_field || !cJSON_IsString(result_field)) {
        fprintf(stderr, "Error: Transmission response is missing result field\n");
        return -1;
    }

    return strcmp(result_field->valuestring, "success");
}

static int transmission_add_torrent_response_get_torrent_name(cJSON *json_root, int *torrent_id, const char **torrent_name) {
    cJSON *arguments_obj = cJSON_GetObjectItemCaseSensitive(json_root, "arguments");
    if(!cJSON_IsObject(arguments_obj)) {
        fprintf(stderr, "Error: transmission add torrent response is missing arguments field or its not an object\n");
        return -1;
    }

    cJSON *torrent_added_obj = cJSON_GetObjectItemCaseSensitive(arguments_obj, "torrent-added");
    if(!cJSON_IsObject(torrent_added_obj))
        torrent_added_obj = cJSON_GetObjectItemCaseSensitive(arguments_obj, "torrent-duplicate");
    
    if(!cJSON_IsObject(torrent_added_obj)) {
        fprintf(stderr, "Error: transmission add torrent response is missing both torrent-added and torrent-duplicate argument\n");
        return -1;
    }

    if(torrent_id) {
        cJSON *torrent_id_field = cJSON_GetObjectItemCaseSensitive(torrent_added_obj, "id");
        if(!cJSON_IsNumber(torrent_id_field)) {
            fprintf(stderr, "Error: transmission add torrent response is missing torrent id\n");
            return -1;
        }

        *torrent_id = torrent_id_field->valuedouble;
        if(*torrent_id == 0) {
            fprintf(stderr, "Error: transmission add torrent response has invalid torrent id\n");
            return -1;
        }
    }

    if(torrent_name) {
        cJSON *torrent_name_field = cJSON_GetObjectItemCaseSensitive(torrent_added_obj, "name");
        if(!cJSON_IsString(torrent_name_field)) {
            fprintf(stderr, "Error: transmission add torrent response is missing torrent name\n");
            return -1;
        }
        *torrent_name = torrent_name_field->valuestring;
    }
    
    return 0;
}

int transmission_add_torrent(TransmissionSession *session, const char *url, int *torrent_id, char **torrent_name) {
    int result = 0;
    Buffer buffer;
    buffer_init(&buffer);
    cJSON *json_response = NULL;

    /* TODO: json escape url */
    char request[4096];
    int written_bytes = snprintf(request, sizeof(request), "{ \"arguments\": { \"filename\": \"%s\" }, \"method\": \"torrent-add\" }", url);
    if(written_bytes >= (int)sizeof(request)) {
        fprintf(stderr, "Failed to write all bytes to request (this is a bug)\n");
        result = -1;
        goto cleanup;
    }

    const char *args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL };
    result = program_exec(args, program_buffer_write_callback, &buffer);
    if(result != 0) {
        /* Maybe the request failed because the session is no longer valid? retry once with new session id */
        if(transmission_connect(session) != 0) {
            fprintf(stderr, "Failed to add torrent: %s\n", url);
            goto cleanup;
        }

        buffer_clear(&buffer);
        const char *new_args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL };
        result = program_exec(new_args, program_buffer_write_callback, &buffer);
        if(result != 0) {
            fprintf(stderr, "Failed to add torrent: %s\n", url);
            goto cleanup;
        }
    }

    json_response = cJSON_ParseWithLength(buffer.data, buffer.size);
    if(!json_response || !cJSON_IsObject(json_response)) {
        fprintf(stderr, "Failed to parse torrent add response: %.*s as json\n", (int)buffer.size, (char*)buffer.data);
        result = -1;
        goto cleanup;
    }
    
    if(transmission_response_is_success(json_response) != 0) {
        fprintf(stderr, "Error: transmission torrent add request failed, response: %.*s\n", (int)buffer.size, (char*)buffer.data);
        result = -1;
        goto cleanup;
    }

    int response_torrent_id;
    const char *response_torrent_name;
    result = transmission_add_torrent_response_get_torrent_name(json_response,
        torrent_id ? &response_torrent_id : NULL,
        torrent_name ? &response_torrent_name : NULL);

    if(result != 0)
        goto cleanup;

    if(torrent_id)
        *torrent_id = response_torrent_id;

    if(torrent_name)
        *torrent_name = strdup(response_torrent_name);

    cleanup:
    cJSON_Delete(json_response);
    buffer_deinit(&buffer);
    return result;
}

int transmission_list_torrents(TransmissionSession *session, TorrentListCallback callback, void *userdata) {
    int result = 0;
    Buffer buffer;
    buffer_init(&buffer);
    cJSON *json_response = NULL;

    char request[100];
    int written_bytes = snprintf(request, sizeof(request), "{ \"arguments\": { \"fields\": [ \"id\", \"name\", \"percentDone\" ] }, \"method\": \"torrent-get\" }");
    if(written_bytes >= (int)sizeof(request)) {
        fprintf(stderr, "Failed to write all bytes to request (this is a bug)\n");
        result = -1;
        goto cleanup;
    }

    const char *args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL };
    result = program_exec(args, program_buffer_write_callback, &buffer);
    if(result != 0) {
        /* Maybe the request failed because the session is no longer valid? retry once with new session id */
        if(transmission_connect(session) != 0) {
            fprintf(stderr, "Failed to list torrents\n");
            goto cleanup;
        }

        buffer_clear(&buffer);
        const char *new_args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL };
        result = program_exec(new_args, program_buffer_write_callback, &buffer);
        if(result != 0) {
            fprintf(stderr, "Failed to list torrents\n");
            goto cleanup;
        }
    }

    json_response = cJSON_ParseWithLength(buffer.data, buffer.size);
    if(!json_response || !cJSON_IsObject(json_response)) {
        fprintf(stderr, "Failed to parse torrent list response: %.*s as json\n", (int)buffer.size, (char*)buffer.data);
        result = -1;
        goto cleanup;
    }
    
    if(transmission_response_is_success(json_response) != 0) {
        fprintf(stderr, "Error: transmission torrent list request failed, response: %.*s\n", (int)buffer.size, (char*)buffer.data);
        result = -1;
        goto cleanup;
    }

    cJSON *arguments_field = cJSON_GetObjectItemCaseSensitive(json_response, "arguments");
    if(!cJSON_IsObject(arguments_field)) {
        fprintf(stderr, "Error: transmission torrent list response is missing arguments or its not an object\n");
        result = -1;
        goto cleanup;
    }

    cJSON *torrents_field = cJSON_GetObjectItemCaseSensitive(arguments_field, "torrents");
    if(!cJSON_IsArray(torrents_field)) {
        fprintf(stderr, "Error: transmission torrent list response is missing torrents or its not an array\n");
        result = -1;
        goto cleanup;
    }

    cJSON *torrent_item = NULL;
    cJSON_ArrayForEach(torrent_item, torrents_field) {
        if(!cJSON_IsObject(torrent_item))
            continue;

        cJSON *id_field = cJSON_GetObjectItemCaseSensitive(torrent_item, "id");
        cJSON *name_field = cJSON_GetObjectItemCaseSensitive(torrent_item, "name");
        cJSON *percent_done_field = cJSON_GetObjectItemCaseSensitive(torrent_item, "percentDone");
        if(!cJSON_IsNumber(id_field) || !cJSON_IsString(name_field) || !cJSON_IsNumber(percent_done_field))
            continue;

        callback(id_field->valuedouble, name_field->valuestring, percent_done_field->valuedouble, userdata);
    }

    cleanup:
    cJSON_Delete(json_response);
    buffer_deinit(&buffer);
    return result;
}