#include "unity.h"
#include "status_monitor.h"
#include "midea_protocol.h"
#include "uart_driver.h"
#include <string.h>

void setUp(void) {}
void tearDown(void) {}

// Build a valid MideaUART status response frame for testing.
// data[0..7] follow the decoder layout:
//   d0: (mode & 0x0F) | (power << 4)
//   d1,d2: indoor_temp (LE)
//   d3,d4: target_temp (LE)
//   d5: (fan & 0x0F) | (error << 4)
//   d6: alarm low, d7: alarm high
static size_t build_status_frame(uint8_t *buf, uint8_t mode, uint8_t power,
                                 int16_t indoor, int16_t target,
                                 uint8_t fan, uint8_t error, uint16_t alarm) {
    size_t o = 0;
    buf[o++] = 0xAA;
    buf[o++] = 0x55;
    buf[o++] = 8;      // length
    buf[o++] = 0x07;   // status command
    buf[o++] = (mode & 0x0F) | ((power & 0x01) << 4);
    buf[o++] = indoor & 0xFF;
    buf[o++] = (indoor >> 8) & 0xFF;
    buf[o++] = target & 0xFF;
    buf[o++] = (target >> 8) & 0xFF;
    buf[o++] = (fan & 0x0F) | ((error & 0x0F) << 4);
    buf[o++] = alarm & 0xFF;
    buf[o++] = (alarm >> 8) & 0xFF;
    uint8_t chk = 0;
    for (size_t i = 2; i < o; i++) chk ^= buf[i];
    buf[o++] = chk;
    return o;
}

void test_status_monitor_init_defaults(void) {
    status_monitor_t m;
    TEST_ASSERT_TRUE(status_monitor_init(&m, NULL));
    TEST_ASSERT_TRUE(m.initialized);
    TEST_ASSERT_EQUAL_UINT32(5000, m.config.poll_interval_ms);
    TEST_ASSERT_EQUAL_UINT32(1000, m.config.timeout_ms);
    TEST_ASSERT_EQUAL_UINT8(3, m.config.max_retries);
    TEST_ASSERT_FALSE(m.last_poll_success);
    TEST_ASSERT_FALSE(m.fault_detected);
}

void test_status_monitor_init_custom_config(void) {
    status_monitor_config_t cfg = { .poll_interval_ms = 2000, .timeout_ms = 500, .max_retries = 5 };
    status_monitor_t m;
    TEST_ASSERT_TRUE(status_monitor_init(&m, &cfg));
    TEST_ASSERT_EQUAL_UINT32(2000, m.config.poll_interval_ms);
    TEST_ASSERT_EQUAL_UINT32(500, m.config.timeout_ms);
    TEST_ASSERT_EQUAL_UINT8(5, m.config.max_retries);
}

void test_status_monitor_init_null(void) {
    TEST_ASSERT_FALSE(status_monitor_init(NULL, NULL));
}

void test_build_request_frame(void) {
    uint8_t req[8];
    size_t len = status_monitor_build_request(req, sizeof(req));
    TEST_ASSERT_EQUAL_UINT(5, len);
    TEST_ASSERT_EQUAL_UINT8(0xAA, req[0]);
    TEST_ASSERT_EQUAL_UINT8(0x55, req[1]);
    TEST_ASSERT_EQUAL_UINT8(0x07, req[3]);
    // checksum = len ^ cmd = 1 ^ 0x07
    TEST_ASSERT_EQUAL_UINT8((uint8_t)(1 ^ 0x07), req[4]);
}

void test_build_request_buffer_too_small(void) {
    uint8_t req[3];
    TEST_ASSERT_EQUAL_UINT(0, status_monitor_build_request(req, sizeof(req)));
    TEST_ASSERT_EQUAL_UINT(0, status_monitor_build_request(NULL, 8));
}

void test_process_response_success(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0);

    midea_status_t status;
    TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status));
    TEST_ASSERT_EQUAL_UINT8(MODE_COOL, status.mode);
    TEST_ASSERT_EQUAL_INT16(2300, status.indoor_temp);
    TEST_ASSERT_EQUAL_INT16(2400, status.target_temp);
    TEST_ASSERT_TRUE(m.last_poll_success);
    TEST_ASSERT_EQUAL_UINT32(1000, m.last_poll_time);
    TEST_ASSERT_EQUAL_UINT8(0, m.retry_count);
    TEST_ASSERT_FALSE(m.fault_detected);
}

void test_process_response_decode_failure(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    // Bad header -> decode fails -> counts as error.
    uint8_t bad[12] = { 0x00, 0x00, 8, 0x07, 0, 0, 0, 0, 0, 0, 0, 0 };
    midea_status_t status;
    TEST_ASSERT_FALSE(status_monitor_process_response(&m, bad, sizeof(bad), 500, &status));
    TEST_ASSERT_FALSE(m.last_poll_success);
    TEST_ASSERT_EQUAL_UINT8(1, m.retry_count);
    TEST_ASSERT_EQUAL_UINT32(1, m.error_count);
}

void test_process_response_null_buffer(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);
    midea_status_t status;
    TEST_ASSERT_FALSE(status_monitor_process_response(&m, NULL, 0, 100, &status));
    TEST_ASSERT_EQUAL_UINT8(1, m.retry_count);
}

void test_fault_detection_error_code(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 3, 0);
    midea_status_t status;
    TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status));
    TEST_ASSERT_EQUAL_UINT8(3, status.error_code);
    TEST_ASSERT_TRUE(status_monitor_has_fault(&m));
    TEST_ASSERT_TRUE(m.fault_code != 0);
}

void test_fault_detection_alarm_mask(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_HEAT, 1, 2000, 2100, 1, 0, 0x0004);
    midea_status_t status;
    TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status));
    TEST_ASSERT_EQUAL_UINT16(0x0004, status.alarm_mask);
    TEST_ASSERT_TRUE(status_monitor_has_fault(&m));
}

void test_fault_cleared_on_good_status(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uint8_t frame[16];
    // First a faulty status.
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 5, 0);
    midea_status_t status;
    status_monitor_process_response(&m, frame, len, 1000, &status);
    TEST_ASSERT_TRUE(status_monitor_has_fault(&m));

    // Then a clean status clears the fault.
    len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0);
    status_monitor_process_response(&m, frame, len, 2000, &status);
    TEST_ASSERT_FALSE(status_monitor_has_fault(&m));
    TEST_ASSERT_EQUAL_UINT16(0, m.fault_code);
}

void test_handle_error_trips_watchdog(void) {
    status_monitor_config_t cfg = { .poll_interval_ms = 5000, .timeout_ms = 1000, .max_retries = 3 };
    status_monitor_t m;
    status_monitor_init(&m, &cfg);

    status_monitor_handle_error(&m);
    TEST_ASSERT_EQUAL_UINT8(1, m.retry_count);
    TEST_ASSERT_FALSE(m.comm_timeout);

    status_monitor_handle_error(&m);
    TEST_ASSERT_EQUAL_UINT8(2, m.retry_count);
    TEST_ASSERT_FALSE(m.comm_timeout);

    status_monitor_handle_error(&m);
    TEST_ASSERT_EQUAL_UINT8(3, m.retry_count);
    TEST_ASSERT_TRUE(m.comm_timeout);
    TEST_ASSERT_EQUAL_UINT32(3, m.error_count);
}

void test_check_timeout_never_polled(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);
    TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 0));
    TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 100000));
}

void test_check_timeout_within_window(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL); // window = 5000 + 1000 = 6000

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0);
    midea_status_t status;
    status_monitor_process_response(&m, frame, len, 1000, &status);

    // 1000 -> 5000 : within 6000ms window
    TEST_ASSERT_FALSE(status_monitor_check_timeout(&m, 5000));
}

void test_check_timeout_exceeded(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL); // window = 6000

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0);
    midea_status_t status;
    status_monitor_process_response(&m, frame, len, 1000, &status);

    // 1000 -> 8000 = 7000ms elapsed > 6000ms window
    TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 8000));
}

void test_check_timeout_uninitialized(void) {
    status_monitor_t m;
    memset(&m, 0, sizeof(m));
    TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 100));
    TEST_ASSERT_TRUE(status_monitor_check_timeout(NULL, 100));
}

void test_poll_uart_no_data(void) {
    // The mock UART receive returns 0 bytes -> poll should fail and count error.
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uart_config_t ucfg = { .tx_pin = 1, .rx_pin = 2, .baud_rate = 9600,
                           .data_bits = 8, .parity = 0, .stop_bits = 1 };
    uart_driver_t uart;
    TEST_ASSERT_TRUE(uart_driver_init(&uart, &ucfg));

    midea_status_t status;
    TEST_ASSERT_FALSE(status_monitor_poll(&m, &uart, 1000, &status));
    TEST_ASSERT_FALSE(m.last_poll_success);
    TEST_ASSERT_EQUAL_UINT8(1, m.retry_count);
    TEST_ASSERT_EQUAL_UINT32(1000, m.last_attempt_time);
}

void test_poll_null_args(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);
    midea_status_t status;
    TEST_ASSERT_FALSE(status_monitor_poll(&m, NULL, 0, &status));
    TEST_ASSERT_FALSE(status_monitor_poll(NULL, NULL, 0, &status));
}

void test_map_to_zcl_modes(void) {
    midea_status_t s;
    zcl_thermostat_attrs_t z;
    memset(&s, 0, sizeof(s));

    // Power off -> Off regardless of mode
    s.power_state = 0; s.mode = MODE_COOL; s.indoor_temp = 2500;
    status_monitor_map_to_zcl(&s, &z);
    TEST_ASSERT_EQUAL_UINT8(0x00, z.system_mode);
    TEST_ASSERT_EQUAL_INT16(2500, z.local_temperature);

    s.power_state = 1;
    s.mode = MODE_COOL; status_monitor_map_to_zcl(&s, &z);
    TEST_ASSERT_EQUAL_UINT8(0x03, z.system_mode);
    s.mode = MODE_HEAT; status_monitor_map_to_zcl(&s, &z);
    TEST_ASSERT_EQUAL_UINT8(0x04, z.system_mode);
    s.mode = MODE_AUTO; status_monitor_map_to_zcl(&s, &z);
    TEST_ASSERT_EQUAL_UINT8(0x01, z.system_mode);
    s.mode = MODE_DRY; status_monitor_map_to_zcl(&s, &z);
    TEST_ASSERT_EQUAL_UINT8(0x08, z.system_mode);
}

void test_get_last_status_and_zcl(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_HEAT, 1, 2100, 2200, 3, 0, 0);
    midea_status_t status;
    status_monitor_process_response(&m, frame, len, 1000, &status);

    midea_status_t got;
    TEST_ASSERT_TRUE(status_monitor_get_last_status(&m, &got));
    TEST_ASSERT_EQUAL_UINT8(MODE_HEAT, got.mode);
    TEST_ASSERT_EQUAL_INT16(2100, got.indoor_temp);

    zcl_thermostat_attrs_t z;
    TEST_ASSERT_TRUE(status_monitor_get_last_zcl_attrs(&m, &z));
    TEST_ASSERT_EQUAL_UINT8(0x04, z.system_mode);
    TEST_ASSERT_EQUAL_INT16(2100, z.local_temperature);

    TEST_ASSERT_FALSE(status_monitor_get_last_status(&m, NULL));
    TEST_ASSERT_FALSE(status_monitor_get_last_status(NULL, &got));
}

void test_successful_poll_resets_retry(void) {
    status_monitor_t m;
    status_monitor_init(&m, NULL);

    // Accumulate errors
    status_monitor_handle_error(&m);
    status_monitor_handle_error(&m);
    TEST_ASSERT_EQUAL_UINT8(2, m.retry_count);

    // A good response resets retry_count and clears comm_timeout
    uint8_t frame[16];
    size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0);
    midea_status_t status;
    TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 3000, &status));
    TEST_ASSERT_EQUAL_UINT8(0, m.retry_count);
    TEST_ASSERT_FALSE(m.comm_timeout);
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_status_monitor_init_defaults);
    RUN_TEST(test_status_monitor_init_custom_config);
    RUN_TEST(test_status_monitor_init_null);
    RUN_TEST(test_build_request_frame);
    RUN_TEST(test_build_request_buffer_too_small);
    RUN_TEST(test_process_response_success);
    RUN_TEST(test_process_response_decode_failure);
    RUN_TEST(test_process_response_null_buffer);
    RUN_TEST(test_fault_detection_error_code);
    RUN_TEST(test_fault_detection_alarm_mask);
    RUN_TEST(test_fault_cleared_on_good_status);
    RUN_TEST(test_handle_error_trips_watchdog);
    RUN_TEST(test_check_timeout_never_polled);
    RUN_TEST(test_check_timeout_within_window);
    RUN_TEST(test_check_timeout_exceeded);
    RUN_TEST(test_check_timeout_uninitialized);
    RUN_TEST(test_poll_uart_no_data);
    RUN_TEST(test_poll_null_args);
    RUN_TEST(test_map_to_zcl_modes);
    RUN_TEST(test_get_last_status_and_zcl);
    RUN_TEST(test_successful_poll_resets_retry);
    return UNITY_END();
}
