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

// Default configuration values
#define DEFAULT_POLL_INTERVAL_MS 5000
#define DEFAULT_TIMEOUT_MS 1000
#define DEFAULT_MAX_RETRIES 3

// Maximum expected response frame size
#define STATUS_RESPONSE_MAX 32

bool status_monitor_init(status_monitor_t *monitor, const status_monitor_config_t *config) {
    if (!monitor) {
        return false;
    }

    memset(monitor, 0, sizeof(status_monitor_t));

    monitor->initialized = true;
    monitor->last_poll_success = false;
    monitor->last_poll_time = 0;
    monitor->last_attempt_time = 0;
    monitor->retry_count = 0;
    monitor->error_count = 0;
    monitor->comm_timeout = false;
    monitor->fault_detected = false;
    monitor->fault_code = 0;

    // Apply configuration with fallback to defaults
    if (config) {
        monitor->config.poll_interval_ms =
            config->poll_interval_ms ? config->poll_interval_ms : DEFAULT_POLL_INTERVAL_MS;
        monitor->config.timeout_ms =
            config->timeout_ms ? config->timeout_ms : DEFAULT_TIMEOUT_MS;
        monitor->config.max_retries =
            config->max_retries ? config->max_retries : DEFAULT_MAX_RETRIES;
    } else {
        monitor->config.poll_interval_ms = DEFAULT_POLL_INTERVAL_MS;
        monitor->config.timeout_ms = DEFAULT_TIMEOUT_MS;
        monitor->config.max_retries = DEFAULT_MAX_RETRIES;
    }

    return true;
}

void status_monitor_deinit(status_monitor_t *monitor) {
    if (monitor) {
        memset(monitor, 0, sizeof(status_monitor_t));
        monitor->initialized = false;
    }
}

size_t status_monitor_build_request(uint8_t *buffer, size_t buffer_size) {
    if (!buffer || buffer_size < 5) {
        return 0;
    }

    // MideaUART status-request frame:
    // [Header 0xAA 0x55][Length 1][Command 0x07 = query][Checksum]
    size_t offset = 0;
    buffer[offset++] = 0xAA;
    buffer[offset++] = 0x55;
    buffer[offset++] = 1;      // length of command payload
    buffer[offset++] = 0x07;   // query/status-request command

    // Checksum: XOR of length + command bytes
    uint8_t checksum = 0;
    for (size_t i = 2; i < offset; i++) {
        checksum ^= buffer[i];
    }
    buffer[offset++] = checksum;

    return offset;
}

// Internal: apply a freshly decoded status to monitor state.
static void status_monitor_apply_status(status_monitor_t *monitor,
                                        const midea_status_t *status,
                                        uint32_t current_time) {
    monitor->last_poll_success = true;
    monitor->last_poll_time = current_time;
    monitor->retry_count = 0;
    monitor->comm_timeout = false;

    memcpy(&monitor->last_status, status, sizeof(midea_status_t));

    // Fault detection: any error code or alarm bit indicates a fault.
    if (status->error_code != 0 || status->alarm_mask != 0) {
        monitor->fault_detected = true;
        monitor->fault_code =
            (uint16_t)(status->error_code) | status->alarm_mask;
    } else {
        monitor->fault_detected = false;
        monitor->fault_code = 0;
    }

    // Keep ZCL attributes in sync for reporting.
    status_monitor_map_to_zcl(status, &monitor->last_zcl_attrs);
}

bool status_monitor_process_response(status_monitor_t *monitor,
                                     const uint8_t *buffer, size_t length,
                                     uint32_t current_time, midea_status_t *status) {
    if (!monitor || !monitor->initialized || !status) {
        return false;
    }

    monitor->last_attempt_time = current_time;

    midea_status_t decoded;
    memset(&decoded, 0, sizeof(decoded));

    if (!buffer || length == 0 || !midea_protocol_decode(buffer, length, &decoded)) {
        // Decode failed -> treat as a communication error.
        status_monitor_handle_error(monitor);
        return false;
    }

    status_monitor_apply_status(monitor, &decoded, current_time);
    memcpy(status, &decoded, sizeof(midea_status_t));
    return true;
}

bool status_monitor_poll(status_monitor_t *monitor, uart_driver_t *uart,
                         uint32_t current_time, midea_status_t *status) {
    if (!monitor || !monitor->initialized || !uart || !status) {
        return false;
    }

    monitor->last_attempt_time = current_time;

    // Build and send a status-request frame.
    uint8_t request[8];
    size_t request_len = status_monitor_build_request(request, sizeof(request));
    if (request_len == 0 ||
        !uart_driver_send(uart, request, request_len, monitor->config.timeout_ms)) {
        status_monitor_handle_error(monitor);
        return false;
    }

    // Receive the response.
    uint8_t response[STATUS_RESPONSE_MAX];
    int received = uart_driver_receive(uart, response, sizeof(response),
                                       monitor->config.timeout_ms);
    if (received <= 0) {
        // No data / error -> communication failure.
        status_monitor_handle_error(monitor);
        return false;
    }

    return status_monitor_process_response(monitor, response, (size_t)received,
                                           current_time, status);
}

void status_monitor_map_to_zcl(const midea_status_t *status, zcl_thermostat_attrs_t *zcl_attrs) {
    if (!status || !zcl_attrs) {
        return;
    }

    // Map MideaUART status to ZCL Thermostat attributes
    zcl_attrs->local_temperature = status->indoor_temp;

    // Map power state and mode to ZCL system_mode
    if (status->power_state == 0x00) {
        // AC is OFF
        zcl_attrs->system_mode = 0x00; // Off
    } else {
        // AC is ON, map the mode
        switch (status->mode) {
            case MODE_COOL:
                zcl_attrs->system_mode = 0x03; // Cooling
                break;
            case MODE_HEAT:
                zcl_attrs->system_mode = 0x04; // Heating
                break;
            case MODE_AUTO:
                zcl_attrs->system_mode = 0x01; // Auto
                break;
            case MODE_DRY:
                zcl_attrs->system_mode = 0x08; // Dry
                break;
            case MODE_FAN:
                zcl_attrs->system_mode = 0x00; // Off (treat fan only as off for HVAC)
                break;
            default:
                zcl_attrs->system_mode = 0x00; // Default to Off
                break;
        }
    }

    // Set control sequence (simplified)
    zcl_attrs->control_sequence = 0x00; // Not used in basic implementation
}

bool status_monitor_check_timeout(const status_monitor_t *monitor, uint32_t current_time) {
    if (!monitor || !monitor->initialized) {
        return true; // Treat uninitialized monitor as timed out
    }

    // Never successfully polled -> considered timed out.
    if (!monitor->last_poll_success) {
        return true;
    }

    // Watchdog window: one poll interval plus the per-attempt timeout allowance.
    uint32_t window = monitor->config.poll_interval_ms + monitor->config.timeout_ms;

    // Guard against clock going backwards.
    if (current_time < monitor->last_poll_time) {
        return false;
    }

    return (current_time - monitor->last_poll_time) > window;
}

void status_monitor_handle_error(status_monitor_t *monitor) {
    if (!monitor) {
        return;
    }

    monitor->last_poll_success = false;
    monitor->error_count++;

    if (monitor->retry_count < 0xFF) {
        monitor->retry_count++;
    }

    // Trip the watchdog once retries are exhausted.
    if (monitor->retry_count >= monitor->config.max_retries) {
        monitor->comm_timeout = true;
    }
}

bool status_monitor_has_fault(const status_monitor_t *monitor) {
    if (!monitor) {
        return false;
    }
    return monitor->fault_detected;
}

bool status_monitor_get_last_status(const status_monitor_t *monitor, midea_status_t *status) {
    if (!monitor || !status) {
        return false;
    }

    memcpy(status, &monitor->last_status, sizeof(midea_status_t));
    return true;
}

bool status_monitor_get_last_zcl_attrs(const status_monitor_t *monitor, zcl_thermostat_attrs_t *zcl_attrs) {
    if (!monitor || !zcl_attrs) {
        return false;
    }

    memcpy(zcl_attrs, &monitor->last_zcl_attrs, sizeof(zcl_thermostat_attrs_t));
    return true;
}
