/*
 * QSJNIC MCU protocol standalone test — beta12 reference edition.
 *
 * Built directly from the Service native L1..L3 implementation
 * (service/src/main/cpp/mcu_protocol.c, mcu_transport.c, mcu_upgrade.c)
 * so the wire behavior is byte-for-byte identical to the production SystemService.
 *
 * Usage:
 *   ./qsjnic_test /dev/ttyS7 --all
 *   ./qsjnic_test /dev/ttyS7 --public
 *   ./qsjnic_test /dev/ttyS7 --radio
 *   ./qsjnic_test /dev/ttyS7 --upgrade-mcu --bin /data/local/tmp/mcu_app.bin
 */

#define _POSIX_C_SOURCE 200809L

#include "mcu_protocol.h"
#include "mcu_transport.h"
#include "mcu_upgrade.h"

#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define DEFAULT_PORT      "/dev/ttyS7"
#define DEFAULT_BAUDRATE  460800

#define ACK_TIMEOUT_MS    2500

static int g_total  = 0;
static int g_passed = 0;
static int g_failed = 0;

static FILE *g_report = NULL;

static void log_line(const char *fmt, ...)
{
    struct timespec ts;
    struct tm tmv;
    char tbuf[32];

    clock_gettime(CLOCK_REALTIME, &ts);
    localtime_r(&ts.tv_sec, &tmv);
    snprintf(tbuf, sizeof(tbuf), "%02d:%02d:%02d.%03d",
             tmv.tm_hour, tmv.tm_min, tmv.tm_sec, (int)(ts.tv_nsec / 1000000));

    va_list ap;
    va_start(ap, fmt);
    printf("[%s] ", tbuf);
    vprintf(fmt, ap);
    printf("\n");
    va_end(ap);

    if (g_report) {
        va_start(ap, fmt);
        fprintf(g_report, "[%s] ", tbuf);
        vfprintf(g_report, fmt, ap);
        fprintf(g_report, "\n");
        fflush(g_report);
        va_end(ap);
    }
}

static void test_result(const char *desc, bool pass)
{
    g_total++;
    if (pass) {
        g_passed++;
        log_line("[ PASS ] %s", desc);
    } else {
        g_failed++;
        log_line("[ FAIL ] %s", desc);
    }
}

static void print_frame(const char *prefix, const McuFrame *frame)
{
    char hex[MCU_MAX_FRAME_LEN * 3 + 1];
    size_t i;
    int pos = 0;
    for (i = 0; i < frame->raw_len && pos < (int)sizeof(hex) - 4; i++) {
        pos += snprintf(hex + pos, sizeof(hex) - pos, "%02X ", frame->raw[i]);
    }
    log_line("%s ft=%s seq=0x%02X payload_len=%zu hex: %s",
             prefix, mcu_frame_type_name(frame->ft), frame->seq,
             frame->payload_len, hex);
}

/* --------------------------------------------------------------- TX helpers */

static bool send_business_frame(McuLink *ctx, uint8_t msg_type, uint8_t cmd,
                                const uint8_t *body, size_t body_len)
{
    uint8_t payload[MCU_MAX_PAYLOAD_LEN];
    if (body_len + 2U > sizeof(payload)) {
        return false;
    }
    payload[0] = msg_type;
    payload[1] = cmd;
    if (body_len > 0U) {
        memcpy(payload + 2U, body, body_len);
    }
    return mcu_link_send_and_wait_ack_with_retry(ctx, FT_DATA, payload, body_len + 2U, NULL);
}

/* --------------------------------------------------------------- notify cb */

static void on_mcu_frame_notify(const McuFrame *frame, void *user)
{
    (void)user;
    print_frame("[MCU->ARM]", frame);
}

/* --------------------------------------------------------------- Public suite */

static bool run_public_suite(McuLink *ctx)
{
    bool ok = true;

    log_line("=== Public suite start ===");

    {
        uint8_t body[] = {0x01};
        ok = send_business_frame(ctx, MT_PUBLIC, 0x81, body, sizeof(body));
        test_result("REQ_INIT_INFO (0x01)", ok);
    }

    {
        uint8_t body[] = {0x02}; /* MCU_VERSION */
        ok = send_business_frame(ctx, MT_PUBLIC, 0x86, body, sizeof(body));
        test_result("REQ_TYPE_INFO MCU_VERSION (0x86)", ok);
    }

    {
        uint8_t body[] = {0x32}; /* 50% duty */
        ok = send_business_frame(ctx, MT_PUBLIC, 0x84, body, sizeof(body));
        test_result("TFT_DUTY_CYCLE (0x84)", ok);
    }

    {
        uint8_t body[] = {0x09}; /* TFT ON */
        ok = send_business_frame(ctx, MT_PUBLIC, 0x88, body, sizeof(body));
        test_result("SET_TFT_ON_OFF (0x88)", ok);
    }

    {
        uint8_t body[] = {0x01}; /* AMP PWR ON */
        ok = send_business_frame(ctx, MT_PUBLIC, 0x89, body, sizeof(body));
        test_result("SET_AMP_PWR (0x89)", ok);
    }

    {
        uint8_t body[] = {0x00}; /* MUTE OFF */
        ok = send_business_frame(ctx, MT_PUBLIC, 0x82, body, sizeof(body));
        test_result("REQ_MUTE (0x82)", ok);
    }

    {
        /* TIME_INFO: 20YY-MM-DD HH:MM:SS */
        uint8_t body[] = {0x01, 0x26, 0x07, 0x03, 0x05, 0x02, 0x36};
        ok = send_business_frame(ctx, MT_PUBLIC, 0x83, body, sizeof(body));
        test_result("TIME_INFO (0x83)", ok);
    }

    log_line("=== Public suite end ===");
    return true;
}

/* ---------------------------------------------------------------- Radio suite */

static bool run_radio_suite(McuLink *ctx)
{
    bool ok = true;

    log_line("=== Radio suite start ===");

    {
        uint8_t body[] = {0x00}; /* FM */
        ok = send_business_frame(ctx, MT_RADIO, 0x81, body, sizeof(body));
        test_result("SET_BAND FM (0x81)", ok);
    }

    {
        uint8_t body[] = {0x00, 0x22, 0x2E, 0x0A}; /* FM range + step */
        ok = send_business_frame(ctx, MT_RADIO, 0x82, body, sizeof(body));
        test_result("FREQ_RANGE (0x82)", ok);
    }

    {
        uint8_t body[] = {0x22, 0x2E}; /* 87.5 MHz */
        ok = send_business_frame(ctx, MT_RADIO, 0x83, body, sizeof(body));
        test_result("SET_FREQ 87.5 (0x83)", ok);
    }

    {
        uint8_t body[] = {0x01}; /* ANT PWR ON */
        ok = send_business_frame(ctx, MT_RADIO, 0x84, body, sizeof(body));
        test_result("ANT_PWR (0x84)", ok);
    }

    {
        uint8_t body[] = {0x01}; /* ON */
        ok = send_business_frame(ctx, MT_RADIO, 0x85, body, sizeof(body));
        test_result("SWITCH_ON_OFF (0x85)", ok);
    }

    {
        uint8_t body[] = {0x00}; /* MUTE OPEN */
        ok = send_business_frame(ctx, MT_RADIO, 0x86, body, sizeof(body));
        test_result("REG_MUTE (0x86)", ok);
    }

    {
        uint8_t body[] = {0x02}; /* FORWARD */
        ok = send_business_frame(ctx, MT_RADIO, 0x87, body, sizeof(body));
        test_result("SEEK_MODE FORWARD (0x87)", ok);
    }

    {
        uint8_t body[] = {0x00}; /* STOP */
        ok = send_business_frame(ctx, MT_RADIO, 0x87, body, sizeof(body));
        test_result("SEEK_MODE STOP (0x87)", ok);
    }

    log_line("=== Radio suite end ===");
    return true;
}

/* -------------------------------------------------------------- OTA helpers */

static bool parse_project_offset(const char *s, long *out)
{
    char *end;
    errno = 0;
    *out = strtol(s, &end, 0);
    if (errno != 0 || *end != '\0' || *out < 0) {
        return false;
    }
    return true;
}

static bool load_bin_and_extract_project(const char *path, long proj_offset,
                                          uint8_t project_bytes[5])
{
    FILE *fp = fopen(path, "rb");
    if (!fp) {
        log_line("[FAIL] open %s: %s", path, strerror(errno));
        return false;
    }
    if (fseek(fp, 0L, SEEK_END) != 0) {
        fclose(fp);
        return false;
    }
    long size = ftell(fp);
    if (size <= 0 || (size_t)size > 0xFFFFFFFFUL) {
        fclose(fp);
        return false;
    }
    if (proj_offset + 5L > size) {
        log_line("[FAIL] proj-offset %ld exceeds bin size %ld", proj_offset, size);
        fclose(fp);
        return false;
    }
    fseek(fp, proj_offset, SEEK_SET);
    if (fread(project_bytes, 1U, 5U, fp) != 5U) {
        fclose(fp);
        return false;
    }
    fclose(fp);
    log_line("[OTA] bin_size=%ld project=%02X%02X%02X%02X%02X at offset=%ld",
             size, project_bytes[0], project_bytes[1], project_bytes[2],
             project_bytes[3], project_bytes[4], proj_offset);
    return true;
}

static void upgrade_progress_cb(const McuUpgradeProgress *p, void *user)
{
    (void)user;
    const char *phase_name = "?";
    switch (p->phase) {
    case MCU_UPGRADE_PHASE_REQ_OK:      phase_name = "REQ_OK"; break;
    case MCU_UPGRADE_PHASE_BEGIN_OK:    phase_name = "BEGIN_OK"; break;
    case MCU_UPGRADE_PHASE_LEN_RECEIVED:phase_name = "LEN"; break;
    case MCU_UPGRADE_PHASE_CHUNK_OK:    phase_name = "CHUNK_OK"; break;
    case MCU_UPGRADE_PHASE_END_OK:      phase_name = "END_OK"; break;
    case MCU_UPGRADE_PHASE_COMPLETE:    phase_name = "COMPLETE"; break;
    case MCU_UPGRADE_PHASE_FAIL:        phase_name = "FAIL"; break;
    }
    log_line("[OTA-PROG] phase=%s chunk=%u/%u bytes=%u/%u ack=0x%02X",
             phase_name, p->chunk_index, p->total_chunks,
             p->bytes_done, p->bytes_total, p->ack_code);
}

/* --------------------------------------------------------------------- main */

static void usage(const char *prog)
{
    fprintf(stderr,
            "Usage:\n"
            "  %s <tty> [--public|--radio|--all]\n"
            "  %s <tty> --upgrade-mcu --bin <path> [--proj-offset <N>]\n"
            "\n"
            "Options:\n"
            "  --public        Public suite only\n"
            "  --radio         Radio suite only\n"
            "  --all           Public + Radio (default)\n"
            "  --upgrade-mcu   Real OTA upgrade flow\n"
            "  --bin <path>    MCU firmware bin file\n"
            "  --proj-offset N Offset of 5-byte project ID in bin (default 9280)\n"
            "  --verbose       Print every TX/RX hex frame\n",
            prog, prog);
}

int main(int argc, char **argv)
{
    const char *port = DEFAULT_PORT;
    int baudrate = DEFAULT_BAUDRATE;
    bool do_public = false;
    bool do_radio = false;
    bool do_upgrade = false;
    const char *bin_path = NULL;
    long proj_offset = 9280;
    bool verbose = false;

    if (argc < 2) {
        usage(argv[0]);
        return 1;
    }

    /* First positional arg is the tty, unless it looks like an option. */
    int i = 1;
    if (argv[i][0] != '-') {
        port = argv[i++];
    }

    for (; i < argc; i++) {
        const char *arg = argv[i];
        if (strcmp(arg, "--public") == 0) {
            do_public = true;
        } else if (strcmp(arg, "--radio") == 0) {
            do_radio = true;
        } else if (strcmp(arg, "--all") == 0) {
            do_public = true;
            do_radio = true;
        } else if (strcmp(arg, "--upgrade-mcu") == 0) {
            do_upgrade = true;
        } else if (strcmp(arg, "--bin") == 0 && i + 1 < argc) {
            bin_path = argv[++i];
        } else if (strcmp(arg, "--proj-offset") == 0 && i + 1 < argc) {
            if (!parse_project_offset(argv[++i], &proj_offset)) {
                fprintf(stderr, "Invalid --proj-offset\n");
                return 1;
            }
        } else if (strcmp(arg, "--verbose") == 0) {
            verbose = true;
        } else if (strcmp(arg, "--help") == 0) {
            usage(argv[0]);
            return 0;
        } else {
            fprintf(stderr, "Unknown option: %s\n", arg);
            usage(argv[0]);
            return 1;
        }
    }

    if (!do_public && !do_radio && !do_upgrade) {
        do_public = true;
        do_radio = true;
    }

    if (do_upgrade && bin_path == NULL) {
        fprintf(stderr, "--upgrade-mcu requires --bin <path>\n");
        return 1;
    }

    g_report = fopen("test_report.log", "w");
    if (!g_report) {
        log_line("[WARN] cannot create test_report.log");
    }

    McuLinkConfig config = {
        .port = port,
        .baudrate = baudrate,
        .verbose_hex = verbose,
        .tx_heartbeat_enabled = true,
        .notify_cb = on_mcu_frame_notify,
        .notify_user = NULL,
    };

    McuLink *ctx = NULL;
    log_line("[LINK] opening %s @ %d", port, baudrate);
    if (mcu_link_open(&ctx, &config) != 0) {
        log_line("[FAIL] mcu_link_open failed");
        if (g_report) fclose(g_report);
        return 1;
    }

    if (do_upgrade) {
        uint8_t project_bytes[5] = {0};
        if (!load_bin_and_extract_project(bin_path, proj_offset, project_bytes)) {
            mcu_link_close(ctx);
            if (g_report) fclose(g_report);
            return 1;
        }
        log_line("[OTA] starting real MCU upgrade...");
        McuUpgradeResult rc = mcu_upgrade_run(ctx, bin_path, project_bytes,
                                              upgrade_progress_cb, NULL);
        test_result("OTA upgrade complete", rc == MCU_UPGRADE_OK);
        if (rc != MCU_UPGRADE_OK) {
            log_line("[FAIL] upgrade result=%d", rc);
        }
    } else {
        if (!mcu_link_do_setup(ctx)) {
            log_line("[FAIL] link setup failed");
            mcu_link_close(ctx);
            if (g_report) fclose(g_report);
            return 1;
        }

        if (do_public) {
            run_public_suite(ctx);
        }
        if (do_radio) {
            run_radio_suite(ctx);
        }

        /* Graceful close. */
        mcu_link_wait_for_rx_quiet(ctx, MCU_SUITE_CLOSE_QUIET_MS,
                                   MCU_SUITE_CLOSE_MAX_WAIT_MS, "close");
        mcu_link_send_close(ctx);
        mcu_sleep_ms(MCU_SUITE_CLOSE_SETTLE_MS);
    }

    mcu_link_close(ctx);

    log_line("=== Summary: total=%d passed=%d failed=%d ===",
             g_total, g_passed, g_failed);

    if (g_report) {
        fprintf(g_report, "=== Summary: total=%d passed=%d failed=%d ===\n",
                g_total, g_passed, g_failed);
        fclose(g_report);
    }

    return g_failed == 0 ? 0 : 1;
}
