#include "pv_optimizer.h"
#include "esp_log.h"
#include <math.h>

static const char *TAG = "pv_optimizer";

// internos (fixos, como pediste)
#define PV_MIN_EXPORT_W (50)        // deadband export (anti-oscilações)
#define PV_TOTAL_RAMP_STEP_A (2.0f) // step total por ciclo (como tens loop 5s)
#define DEFAULT_VOLTAGE_V (230.0f)

typedef struct
{
    bool enabled;
    int32_t max_import_w; // >=0
} pv_cfg_t;

static pv_cfg_t s_cfg = {
    .enabled = false,
    .max_import_w = 0};

static float clamp_pf(float pf)
{
    if (pf < 0.05f || pf > 1.2f)
        return 1.0f;
    return pf;
}

static void estimate_v_and_phases(const meter_event_data_t *m, float *v_avg, int *nph)
{
    float sum = 0.0f;
    int cnt = 0;

    if (!m)
    {
        *v_avg = DEFAULT_VOLTAGE_V;
        *nph = 1;
        return;
    }

    for (int i = 0; i < 3; i++)
    {
        if (m->vrms[i] > 80.0f)
        {
            sum += m->vrms[i];
            cnt++;
        }
    }

    if (cnt == 0)
    {
        *v_avg = DEFAULT_VOLTAGE_V;
        *nph = 1;
        return;
    }

    *v_avg = sum / (float)cnt;
    *nph = cnt;
}

void pv_optimizer_init(void)
{
    // nada a fazer
}

void pv_optimizer_set_enabled(bool en) { s_cfg.enabled = en; }
bool pv_optimizer_is_enabled(void) { return s_cfg.enabled; }

esp_err_t pv_optimizer_set_max_import_w(int32_t w)
{
    if (w < 0)
        return ESP_ERR_INVALID_ARG;
    s_cfg.max_import_w = w;
    return ESP_OK;
}

int32_t pv_optimizer_get_max_import_w(void) { return s_cfg.max_import_w; }

static float ramp_total(float last_a, float target_a)
{
    if (target_a > last_a + PV_TOTAL_RAMP_STEP_A)
        return last_a + PV_TOTAL_RAMP_STEP_A;
    if (target_a < last_a - PV_TOTAL_RAMP_STEP_A)
        return last_a - PV_TOTAL_RAMP_STEP_A;
    return target_a;
}

float pv_optimizer_compute_budget_a(const meter_event_data_t *grid_evt,
                                    float last_total_cmd_a,
                                    float total_hw_max_a)
{
    if (!s_cfg.enabled)
        return total_hw_max_a;
    if (!grid_evt)
        return 0.0f;

    // se meter não fornece potência (fica 0) não dá para PV -> conservador: não importa
    // (podes mudar para "mantém last" se preferires)
    if (grid_evt->watt_total == 0)
    {
        return ramp_total(last_total_cmd_a, 0.0f);
    }

    float v_avg;
    int nph;
    estimate_v_and_phases(grid_evt, &v_avg, &nph);

    const float pf = clamp_pf(grid_evt->power_factor);
    const float w_per_a = v_avg * (float)nph * pf;
    if (w_per_a < 10.0f)
    {
        return ramp_total(last_total_cmd_a, 0.0f);
    }

    const int32_t p_grid_w = grid_evt->watt_total;      // +import / -export
    const int32_t target_import_w = s_cfg.max_import_w; // >=0

    // deadband só para o "Só PV"
    if (target_import_w == 0)
    {
        if (p_grid_w < 0)
        {
            int32_t export_w = -p_grid_w;
            if (export_w < PV_MIN_EXPORT_W)
            {
                return ramp_total(last_total_cmd_a, 0.0f);
            }
        }
        else
        {
            // está a importar
            if (p_grid_w < PV_MIN_EXPORT_W)
            {
                return ramp_total(last_total_cmd_a, 0.0f);
            }
        }
    }

    // estima base-load com o comando anterior
    const float p_evse_last_w = last_total_cmd_a * w_per_a;
    const float p_base_w = (float)p_grid_w - p_evse_last_w;

    // queremos p_grid -> target_import_w
    float p_evse_target_w = (float)target_import_w - p_base_w;

    // clamp [0..max]
    if (p_evse_target_w < 0.0f)
        p_evse_target_w = 0.0f;
    const float p_evse_max_w = total_hw_max_a * w_per_a;
    if (p_evse_target_w > p_evse_max_w)
        p_evse_target_w = p_evse_max_w;

    float target_total_a = p_evse_target_w / w_per_a;
    if (target_total_a < 0.0f)
        target_total_a = 0.0f;
    if (target_total_a > total_hw_max_a)
        target_total_a = total_hw_max_a;

    float ramped = ramp_total(last_total_cmd_a, target_total_a);

    ESP_LOGD(TAG, "pv: p_grid=%ldW target_imp=%ldW base=%.1fW last=%.1fA -> target=%.1fA (v=%.1f nph=%d pf=%.2f)",
             (long)p_grid_w, (long)target_import_w, p_base_w, last_total_cmd_a, ramped, v_avg, nph, pf);

    return ramped;
}
