/*
 * serial_parser.c - Parse serial console logs and classify boot stages.
 *
 * Reads the text captured by QEMU's -serial file:serial.log and classifies
 * each line into a boot stage (firmware → bootloader → kernel → hardware →
 * userspace → login). Kernel timestamps in [X.XXXXXX] prefix are used to
 * estimate boot duration.
 */

#include "serial_parser.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

const char *boot_stage_names[STAGE_COUNT] = {
    "unknown",
    "firmware",
    "bootloader",
    "kernel_init",
    "hardware_init",
    "userspace",
    "login",
    "complete",
    "error"
};

static int is_benign_warning(const char *line)
{
    if (strstr(line, "ACPI:") || strstr(line, "ACPI Error:") ||
        strstr(line, "acpi"))
        return 1;

    if (strstr(line, "[drm:") || strstr(line, "drm:") ||
        strstr(line, "DRM:"))
        return 1;

    if (strstr(line, "scheduling while atomic") ||
        strstr(line, "warning:"))
        return 1;

    if (strstr(line, "can't find") || strstr(line, "not found.") ||
        strstr(line, "unknown symbol"))
        return 1;

    return 0;
}

static boot_stage_t classify_line(const char *line)
{
    if (strstr(line, "kernel panic") || strstr(line, "Kernel panic") ||
        strstr(line, "Kernel Panic") || strstr(line, "not syncing."))
        return STAGE_ERROR;

    if ((strstr(line, "BUG:") || strstr(line, "Oops:") ||
         strstr(line, "Call Trace")) && !is_benign_warning(line))
        return STAGE_ERROR;

    if (strstr(line, "BdsDxe") || strstr(line, "OVMF") ||
        strstr(line, "UEFI")   || strstr(line, "SEC:") ||
        strstr(line, "PEI:")   || strstr(line, "DXE:"))
        return STAGE_FIRMWARE;

    if (strstr(line, "grub") || strstr(line, "GRUB") ||
        strstr(line, "GNU GRUB") || strstr(line, "systemd-boot"))
        return STAGE_BOOTLOADER;

    if (strstr(line, "Linux version") || strstr(line, "Booting Linux"))
        return STAGE_KERNEL_INIT;

    if (strstr(line, "login:") || strstr(line, "Login") ||
        strstr(line, "Welcome"))
        return STAGE_LOGIN;

    if (strstr(line, "boot finished") || strstr(line, "Boot complete") ||
        strstr(line, "System finished") || strstr(line, "Reached target"))
        return STAGE_COMPLETE;

    if (strstr(line, "systemd") || strstr(line, "Starting") ||
        strstr(line, "Mounted") || strstr(line, "Started"))
        return STAGE_USERSPACE;

    if (strstr(line, "[drm]") || strstr(line, "usb") || strstr(line, "USB") ||
        strstr(line, "pci")   || strstr(line, "PCI") ||
        strstr(line, "ahci")  || strstr(line, "AHCI") ||
        strstr(line, "SCSI")  || strstr(line, "scsi") ||
        strstr(line, "ata")   || strstr(line, "ATA")  ||
        strstr(line, "virtio")|| strstr(line, "VIRTIO")||
        strstr(line, "input:")|| strstr(line, "eth")  ||
        strstr(line, "acpi")  || strstr(line, "ACPI"))
        return STAGE_HARDWARE_INIT;

    return STAGE_UNKNOWN;
}

static int extract_timestamp(const char *line, double *ts)
{
    const char *p = strchr(line, '[');
    if (!p)
        return -1;
    p++;

    char *end;
    double val = strtod(p, &end);
    if (end == p)
        return -1;
    if (*end != ']')
        return -1;

    *ts = val;
    return 0;
}

static char *read_line(FILE *f)
{
    size_t cap = 256;
    size_t len = 0;
    char *buf = malloc(cap);
    if (!buf)
        return NULL;

    int c;
    while ((c = fgetc(f)) != EOF) {
        if (c == '\n')
            break;
        if (len + 1 >= cap) {
            size_t newcap = cap * 2;
            char *tmp = realloc(buf, newcap);
            if (!tmp) {
                free(buf);
                return NULL;
            }
            buf = tmp;
            cap = newcap;
        }
        buf[len++] = (char)c;
    }

    if (len == 0 && c == EOF) {
        free(buf);
        return NULL;
    }

    buf[len] = '\0';
    if (len > 0 && buf[len - 1] == '\r')
        buf[len - 1] = '\0';

    return buf;
}

int serial_parse_file(const char *path, serial_summary_t *summary)
{
    if (!path || !summary)
        return -1;

    FILE *f = fopen(path, "r");
    if (!f)
        return -1;

    memset(summary, 0, sizeof(*summary));
    summary->final_stage = STAGE_UNKNOWN;

    serial_line_t *tail = NULL;
    double first_ts = -1.0;
    double last_ts  = -1.0;

    for (;;) {
        char *text = read_line(f);
        if (!text)
            break;
        if (text[0] == '\0') {
            free(text);
            continue;
        }

        serial_line_t *sl = calloc(1, sizeof(serial_line_t));
        if (!sl) {
            free(text);
            break;
        }

        sl->text = text;
        sl->stage = classify_line(text);

        if (!summary->lines)
            summary->lines = sl;
        else
            tail->next = sl;
        tail = sl;
        summary->total_lines++;

        if (sl->stage >= 0 && sl->stage < STAGE_COUNT)
            summary->lines_per_stage[sl->stage]++;

        if (sl->stage != STAGE_UNKNOWN) {
            if (sl->stage == STAGE_ERROR) {
                summary->final_stage = STAGE_ERROR;
            } else if (sl->stage > summary->final_stage) {
                summary->final_stage = sl->stage;
            }
        }

        if (sl->stage == STAGE_ERROR && summary->error_count < 8) {
            strncpy(summary->error_messages[summary->error_count],
                    text, 255);
            summary->error_messages[summary->error_count][255] = '\0';
            summary->error_count++;
        }

        double ts;
        if (extract_timestamp(text, &ts) == 0) {
            if (first_ts < 0)
                first_ts = ts;
            last_ts = ts;
        }
    }

    fclose(f);

    if (first_ts >= 0 && last_ts >= first_ts)
        summary->boot_duration_ms = (uint64_t)((last_ts - first_ts) * 1000.0);

    return 0;
}

void serial_free_summary(serial_summary_t *summary)
{
    if (!summary)
        return;

    serial_line_t *sl = summary->lines;
    while (sl) {
        serial_line_t *next = sl->next;
        free(sl->text);
        free(sl);
        sl = next;
    }
    summary->lines = NULL;
    summary->total_lines = 0;
}

void serial_print_stages(const serial_summary_t *summary, FILE *out)
{
    const char *final = (summary->final_stage >= 0 &&
                         summary->final_stage < STAGE_COUNT)
                        ? boot_stage_names[summary->final_stage]
                        : "unknown";

    fprintf(out, "=== Boot Stages ===\n\n");
    fprintf(out, "Final stage: %s\n\n", final);

    fprintf(out, "Lines per stage:\n");
    for (int i = 0; i < STAGE_COUNT; i++) {
        fprintf(out, "  %-20s %5zu%s\n",
                boot_stage_names[i],
                summary->lines_per_stage[i],
                summary->lines_per_stage[i] > 0 ? "  <--" : "");
    }

    if (summary->boot_duration_ms > 0) {
        fprintf(out, "\nBoot duration: %" PRIu64 " ms (%.2f s)\n",
                summary->boot_duration_ms,
                summary->boot_duration_ms / 1000.0);
    }

    if (summary->error_count > 0) {
        fprintf(out, "\nErrors (%d):\n", summary->error_count);
        for (int i = 0; i < summary->error_count; i++)
            fprintf(out, "  %s\n", summary->error_messages[i]);
    }
}

void serial_print_text(const serial_summary_t *summary, FILE *out)
{
    fprintf(out, "=== Serial Console Summary ===\n");
    fprintf(out, "Total lines:      %zu\n", summary->total_lines);

    const char *final = (summary->final_stage >= 0 &&
                         summary->final_stage < STAGE_COUNT)
                        ? boot_stage_names[summary->final_stage]
                        : "unknown";
    fprintf(out, "Final boot stage: %s\n", final);

    if (summary->boot_duration_ms > 0) {
        fprintf(out, "Boot duration:    %" PRIu64 " ms\n",
                summary->boot_duration_ms);
    }

    fprintf(out, "\nLines per stage:\n");
    for (int i = 0; i < STAGE_COUNT; i++) {
        if (summary->lines_per_stage[i] > 0) {
            fprintf(out, "  %-20s %zu\n",
                    boot_stage_names[i],
                    summary->lines_per_stage[i]);
        }
    }

    if (summary->error_count > 0) {
        fprintf(out, "\nErrors (%d):\n", summary->error_count);
        for (int i = 0; i < summary->error_count; i++)
            fprintf(out, "  %s\n", summary->error_messages[i]);
    }
}
