#include "seobeo/seobeo.h"
#include "media_processor/media_processor.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pthread.h>
#include <unistd.h>

#define UPLOAD_DIR "./uploads"
#define PROCESSED_DIR "./media"
#define MAX_UPLOAD_SIZE (100 * 1024 * 1024)  // 100MB

// Background processing queue
typedef struct ProcessJob {
    char* input_path;
    char* id;
    MediaType type;
    struct ProcessJob* next;
} ProcessJob;

static ProcessJob* job_queue_head = NULL;
static ProcessJob* job_queue_tail = NULL;
static pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t job_queue_cond = PTHREAD_COND_INITIALIZER;

// ============================================================================
// Helper Functions
// ============================================================================

static void ensure_directories(void) {
    mkdir(UPLOAD_DIR, 0755);
    mkdir(PROCESSED_DIR, 0755);
}

static char* extract_filename_from_content_disposition(const char* header, Dowa_Arena* arena) {
    const char* filename_start = strstr(header, "filename=\"");
    if (!filename_start) return NULL;

    filename_start += 10;  // Skip 'filename="'
    const char* filename_end = strchr(filename_start, '"');
    if (!filename_end) return NULL;

    size_t len = filename_end - filename_start;
    char* filename = Dowa_Arena_Allocate(arena, len + 1);
    memcpy(filename, filename_start, len);
    filename[len] = '\0';
    return filename;
}

static char* find_multipart_boundary(const char* content_type, Dowa_Arena* arena) {
    const char* boundary_start = strstr(content_type, "boundary=");
    if (!boundary_start) return NULL;

    boundary_start += 9;  // Skip 'boundary='

    // Handle quoted boundary
    if (*boundary_start == '"') {
        boundary_start++;
        const char* boundary_end = strchr(boundary_start, '"');
        if (!boundary_end) return NULL;
        size_t len = boundary_end - boundary_start;
        char* boundary = Dowa_Arena_Allocate(arena, len + 1);
        memcpy(boundary, boundary_start, len);
        boundary[len] = '\0';
        return boundary;
    }

    // Unquoted boundary
    size_t len = strlen(boundary_start);
    char* boundary = Dowa_Arena_Allocate(arena, len + 1);
    strcpy(boundary, boundary_start);
    // Trim whitespace/newline
    while (len > 0 && (boundary[len-1] == '\r' || boundary[len-1] == '\n' || boundary[len-1] == ' ')) {
        boundary[--len] = '\0';
    }
    return boundary;
}

// ============================================================================
// Background Processing Worker
// ============================================================================

static void enqueue_job(const char* input_path, const char* id, MediaType type) {
    ProcessJob* job = malloc(sizeof(ProcessJob));
    job->input_path = strdup(input_path);
    job->id = strdup(id);
    job->type = type;
    job->next = NULL;

    pthread_mutex_lock(&job_queue_mutex);
    if (job_queue_tail) {
        job_queue_tail->next = job;
        job_queue_tail = job;
    } else {
        job_queue_head = job_queue_tail = job;
    }
    pthread_cond_signal(&job_queue_cond);
    pthread_mutex_unlock(&job_queue_mutex);
}

static void* processing_worker(void* arg) {
    (void)arg;

    while (1) {
        pthread_mutex_lock(&job_queue_mutex);
        while (!job_queue_head) {
            pthread_cond_wait(&job_queue_cond, &job_queue_mutex);
        }
        ProcessJob* job = job_queue_head;
        job_queue_head = job->next;
        if (!job_queue_head) job_queue_tail = NULL;
        pthread_mutex_unlock(&job_queue_mutex);

        // Process the job
        Seobeo_Log(SEOBEO_INFO, "Processing media: %s (id=%s)", job->input_path, job->id);

        MediaProcessorOpts opts = media_processor_opts_default(PROCESSED_DIR);
        MediaResult* result = media_process_file(job->input_path, job->id, &opts);

        if (result->status == MEDIA_STATUS_COMPLETED) {
            Seobeo_Log(SEOBEO_INFO, "Processing completed for %s", job->id);
        } else {
            Seobeo_Log(SEOBEO_ERROR, "Processing failed for %s: %s", job->id, result->error_message);
        }

        media_result_free(result);

        // Clean up temp upload file
        unlink(job->input_path);

        free(job->input_path);
        free(job->id);
        free(job);
    }

    return NULL;
}

void Seobeo_Render_Html(
    char *final_body,
    char *template,
    Dowa_Arena *arena
)
{
  size_t current_offset = 0;
  char *cursor = template;

  int32 token_len = 2;

  while (1)
  {
    char *start_tag = strstr(cursor, "{{");
    if (!start_tag) break;

    char *end_tag = strstr(start_tag, "}}");
    if (!end_tag) break;

    size_t leading_len = start_tag - cursor;
    memcpy(final_body + current_offset, cursor, leading_len);
    current_offset += leading_len;

    size_t name_len = end_tag - (start_tag + token_len);
    char *include_name = Dowa_Arena_Allocate(arena, name_len + 1);
    memcpy(include_name, start_tag + token_len, name_len);
    include_name[name_len] = '\0';

    size_t sub_file_size = 0;
    char *sub_content = Seobeo_Web_LoadFile(include_name, &sub_file_size);
    Seobeo_Log(SEOBEO_DEBUG, "[Curr] Sub content: %s\n", sub_content);
    if (sub_content)
    {
      memcpy(final_body + current_offset, sub_content, sub_file_size);
      current_offset += sub_file_size;
      free(sub_content);
    }

    cursor = end_tag + 2;
  }
  strcpy(final_body + current_offset, cursor);
}


void Seobeo_Render_Html_FilePath(
    char *final_body,
    char *path,
    Dowa_Arena *arena
) {
  Seobeo_Log(SEOBEO_DEBUG, "[Curr] %s\n", path);
  size_t html_size = 0;
  char *template = Seobeo_Web_LoadFile(path, &html_size);
  if (!template) return;
  Seobeo_Render_Html(final_body, template, arena);
}

// ============================================================================
// API Route Handlers
// ============================================================================

// GET / - Serve the main HTML page
Seobeo_Request_Entry* handle_index(Seobeo_Request_Entry* req, Dowa_Arena* arena) {
  Seobeo_Request_Entry *resp = NULL; 
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_Render_Html_FilePath(final_body, "/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

// POST /api/upload - Handle file upload
Seobeo_Request_Entry* handle_upload(Seobeo_Request_Entry* req, Dowa_Arena* arena) {
    const char* content_type = Dowa_HashMap_Get(req, "content-type");
    const char* body = Dowa_HashMap_Get(req, "Body");
    const char* content_length_str = Dowa_HashMap_Get(req, "content-length");

    Seobeo_Request_Entry* resp = NULL;

    if (!content_type || !body) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Missing content\"}", arena);
        return resp;
    }

    size_t content_length = content_length_str ? atol(content_length_str) : strlen(body);

    if (content_length > MAX_UPLOAD_SIZE) {
        Dowa_HashMap_Push_Arena(resp, "status", "413", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"File too large\"}", arena);
        return resp;
    }

    // Parse multipart form data
    char* boundary = find_multipart_boundary(content_type, arena);
    if (!boundary) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Invalid multipart data\"}", arena);
        return resp;
    }

    // Find file content in multipart data
    char boundary_marker[256];
    snprintf(boundary_marker, sizeof(boundary_marker), "--%s", boundary);

    const char* part_start = strstr(body, boundary_marker);
    if (!part_start) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"No file found\"}", arena);
        return resp;
    }

    // Skip to headers
    part_start = strstr(part_start, "\r\n");
    if (!part_start) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Invalid format\"}", arena);
        return resp;
    }
    part_start += 2;

    // Find filename from Content-Disposition
    char* filename = NULL;
    const char* header_end = strstr(part_start, "\r\n\r\n");
    if (header_end) {
        char headers[1024] = {0};
        size_t headers_len = header_end - part_start;
        if (headers_len < sizeof(headers)) {
            memcpy(headers, part_start, headers_len);
            filename = extract_filename_from_content_disposition(headers, arena);
        }
    }

    if (!filename) {
        filename = "upload";
    }

    // Find file content
    const char* file_start = header_end + 4;  // Skip \r\n\r\n

    // Find end boundary
    char end_boundary[256];
    snprintf(end_boundary, sizeof(end_boundary), "\r\n--%s", boundary);
    const char* file_end = strstr(file_start, end_boundary);

    if (!file_end) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Incomplete upload\"}", arena);
        return resp;
    }

    size_t file_size = file_end - file_start;

    // Generate ID and determine type
    char* id = media_generate_id();
    MediaType type = media_detect_type(filename);

    if (type == MEDIA_TYPE_UNKNOWN) {
        free(id);
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Unsupported file type\"}", arena);
        return resp;
    }

    // Save to temp file
    const char* ext = media_get_extension(filename);
    char temp_path[512];
    snprintf(temp_path, sizeof(temp_path), "%s/%s%s", UPLOAD_DIR, id, ext ? ext : "");

    FILE* f = fopen(temp_path, "wb");
    if (!f) {
        free(id);
        Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Failed to save file\"}", arena);
        return resp;
    }

    fwrite(file_start, 1, file_size, f);
    fclose(f);

    // Enqueue for processing
    enqueue_job(temp_path, id, type);

    // Return success with ID
    char response_body[256];
    snprintf(response_body, sizeof(response_body),
        "{\"id\":\"%s\",\"type\":\"%s\",\"status\":\"processing\"}",
        id, type == MEDIA_TYPE_IMAGE ? "image" : "video");

    char* body_copy = Dowa_Arena_Allocate(arena, strlen(response_body) + 1);
    strcpy(body_copy, response_body);

    Dowa_HashMap_Push_Arena(resp, "status", "202", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "Body", body_copy, arena);

    free(id);
    return resp;
}

// GET /api/media/:id - Get media info/status
Seobeo_Request_Entry* handle_media_info(Seobeo_Request_Entry* req, Dowa_Arena* arena) {
    const char* path = Dowa_HashMap_Get(req, "path");

    Seobeo_Request_Entry* resp = NULL;

    // Extract ID from path (e.g., /api/media/123456)
    const char* id = path + strlen("/api/media/");
    if (!id || strlen(id) == 0) {
        Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Missing media ID\"}", arena);
        return resp;
    }

    // Check if processed directory exists
    char media_dir[512];
    snprintf(media_dir, sizeof(media_dir), "%s/%s", PROCESSED_DIR, id);

    struct stat st;
    if (stat(media_dir, &st) != 0) {
        // Check if still processing
        char upload_pattern[512];
        snprintf(upload_pattern, sizeof(upload_pattern), "%s/%s", UPLOAD_DIR, id);

        DIR* dir = opendir(UPLOAD_DIR);
        boolean found = FALSE;
        if (dir) {
            struct dirent* entry;
            while ((entry = readdir(dir)) != NULL) {
                if (strncmp(entry->d_name, id, strlen(id)) == 0) {
                    found = TRUE;
                    break;
                }
            }
            closedir(dir);
        }

        if (found) {
            Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
            Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
            Dowa_HashMap_Push_Arena(resp, "Body", "{\"status\":\"processing\"}", arena);
        } else {
            Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
            Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
            Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Media not found\"}", arena);
        }
        return resp;
    }

    // Check what files exist
    char webp_path[512], hls_path[512], thumb_path[512];
    snprintf(webp_path, sizeof(webp_path), "%s/image.webp", media_dir);
    snprintf(hls_path, sizeof(hls_path), "%s/video.m3u8", media_dir);
    snprintf(thumb_path, sizeof(thumb_path), "%s/video_thumb.jpg", media_dir);

    boolean is_image = (stat(webp_path, &st) == 0);
    boolean is_video = (stat(hls_path, &st) == 0);

    char response[1024];
    if (is_image) {
        snprintf(response, sizeof(response),
            "{\"id\":\"%s\",\"type\":\"image\",\"status\":\"completed\","
            "\"webp\":\"/media/%s/image.webp\","
            "\"original\":\"/media/%s/original\"}",
            id, id, id);
    } else if (is_video) {
        snprintf(response, sizeof(response),
            "{\"id\":\"%s\",\"type\":\"video\",\"status\":\"completed\","
            "\"hls\":\"/media/%s/video.m3u8\","
            "\"thumbnail\":\"/media/%s/video_thumb.jpg\","
            "\"original\":\"/media/%s/original\"}",
            id, id, id, id);
    } else {
        snprintf(response, sizeof(response), "{\"id\":\"%s\",\"status\":\"processing\"}", id);
    }

    char* body = Dowa_Arena_Allocate(arena, strlen(response) + 1);
    strcpy(body, response);

    Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "Body", body, arena);

    return resp;
}

// GET /api/media - List all media
Seobeo_Request_Entry* handle_media_list(Seobeo_Request_Entry* req, Dowa_Arena* arena) {
    (void)req;

    Seobeo_Request_Entry* resp = NULL;

    DIR* dir = opendir(PROCESSED_DIR);
    if (!dir) {
        Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
        Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
        Dowa_HashMap_Push_Arena(resp, "Body", "{\"items\":[]}", arena);
        return resp;
    }

    // Build JSON array
    char json[16384] = "{\"items\":[";
    size_t json_len = strlen(json);
    boolean first = TRUE;

    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_name[0] == '.') continue;

        char media_dir[512];
        snprintf(media_dir, sizeof(media_dir), "%s/%s", PROCESSED_DIR, entry->d_name);

        struct stat st;
        if (stat(media_dir, &st) != 0 || !S_ISDIR(st.st_mode)) continue;

        // Check type
        char webp_path[512], hls_path[512];
        snprintf(webp_path, sizeof(webp_path), "%s/image.webp", media_dir);
        snprintf(hls_path, sizeof(hls_path), "%s/video.m3u8", media_dir);

        const char* type = "unknown";
        if (stat(webp_path, &st) == 0) type = "image";
        else if (stat(hls_path, &st) == 0) type = "video";

        char item[256];
        snprintf(item, sizeof(item), "%s{\"id\":\"%s\",\"type\":\"%s\"}",
            first ? "" : ",", entry->d_name, type);

        size_t item_len = strlen(item);
        if (json_len + item_len + 10 < sizeof(json)) {
            strcpy(json + json_len, item);
            json_len += item_len;
            first = FALSE;
        }
    }

    closedir(dir);

    strcpy(json + json_len, "]}");

    char* body = Dowa_Arena_Allocate(arena, strlen(json) + 1);
    strcpy(body, json);

    Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "Body", body, arena);

    return resp;
}

// ============================================================================
// Main
// ============================================================================

int main(int argc, char** argv) {
    const char* port = "8080";

    printf("===========================================\n");
    printf("  Medi - Media Upload & Processing Server\n");
    printf("===========================================\n");
    printf("Starting on port %s...\n\n", port);

    // Create directories
    ensure_directories();

    // Start background processing worker
    pthread_t worker_thread;
    pthread_create(&worker_thread, NULL, processing_worker, NULL);

    // Initialize router
    Seobeo_Router_Init();

    // Register routes
    Seobeo_Router_Register("GET", "/", handle_index);
    Seobeo_Router_Register("POST", "/api/upload", handle_upload);
    Seobeo_Router_Register("GET", "/api/media", handle_media_list);
    Seobeo_Router_Register("GET", "/api/media/*", handle_media_info);

    // Start server (serves static files from ./media for /media/* paths)
    Seobeo_Web_Server_Start("medi/src", port, SEOBEO_MODE_EDGE, 4);

    return 0;
}
