#include "charge_manager.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    char log[32];
    unsigned n;
    char fail_on;
    bool enabled;
    bool ext_clamp;
    uint16_t input_ma, charge_ma;
} mock;

static bool record(mock *m, char op) {
    if (m->n < sizeof(m->log)) m->log[m->n++] = op;
    if (op == m->fail_on) return false;
    return true;
}
static bool charge_enable(void *ctx, bool on) {
    mock *m=ctx; if (!record(m,on?'E':'e')) return false; m->enabled=on; return true;
}
static bool input_limit(void *ctx, uint16_t ma) {
    mock *m=ctx; if (!record(m,'I')) return false; m->input_ma=ma; return true;
}
static bool charge_limit(void *ctx, uint16_t ma) {
    mock *m=ctx; if (!record(m,'C')) return false; m->charge_ma=ma; return true;
}
static bool clamp(void *ctx, bool on) {
    mock *m=ctx; if (!record(m,on?'L':'l')) return false; m->ext_clamp=on; return true;
}
static charge_hal make_hal(mock *m) { return (charge_hal){m,charge_enable,input_limit,charge_limit,clamp}; }
static charge_observation obs(uint8_t s0,uint8_t s1,uint8_t pdo,uint16_t vbus) {
    return (charge_observation){s0,s1,pdo,vbus,0,true,true,true,true};
}

int main(void) {
    uint16_t v=0,ma=0,reg=0,applied=0;
    assert(charge_decode_source(0x26,0x40,0,&v,&ma) && v==9000 && ma==1800);
    assert(charge_decode_source(0x12,0x40,0,&v,&ma) && v==5000 && ma==900);
    assert(charge_decode_source(0x00,0x46,0,&v,&ma) && v==5000 && ma==1800);
    assert(charge_decode_source(0x00,0x44,0,&v,&ma) && v==5000 && ma==450);
    assert(charge_decode_source(0x00,0x40,0x86,&v,&ma) && v==5000 && ma==1800);
    assert(!charge_decode_source(0x00,0x00,0x86,&v,&ma));
    assert(!charge_decode_source(0x36,0x40,0,&v,&ma)); /* 12V not in product input spec */
    assert(!bq25798_encode_iindpm(90,&reg,&applied));
    assert(bq25798_encode_iindpm(1799,&reg,&applied) && reg==179 && applied==1790);
    assert(bq25798_encode_iindpm(3300,&reg,&applied) && reg==330 && applied==3300);
    assert(!bq25798_encode_iindpm(3301,&reg,&applied));

    mock m={0}; charge_hal h=make_hal(&m); charge_report r={0};
    charge_observation o=obs(0x26,0x40,0,9000);
    assert(charge_manager_step(&h,&o,&r)==CHARGE_ACTIVE);
    assert(m.enabled && !m.ext_clamp && m.input_ma==1800 && m.charge_ma==1000);
    assert(m.n==6 && memcmp(m.log,"eLIClE",6)==0); /* CE off -> clamp on -> limits -> unclamp -> CE on */
    unsigned calls=m.n;
    assert(charge_manager_step(&h,&o,&r)==CHARGE_ACTIVE && m.n==calls); /* Stable poll has no writes. */

    o.vbus_mv=5000;
    assert(charge_manager_step(&h,&o,&r)==CHARGE_VBUS_MISMATCH && !m.enabled && m.ext_clamp);
    o=obs(0x12,0x40,0,5000); o.pack_and_ntc_valid=false;
    assert(charge_manager_step(&h,&o,&r)==CHARGE_PACK_OR_TEMPERATURE_FAULT && !m.enabled);
    o=obs(0x12,0x40,0,5000); o.charger_fault_free=false;
    assert(charge_manager_step(&h,&o,&r)==CHARGE_CHARGER_FAULT && !m.enabled);
    o=obs(0x00,0x00,0x86,5000);
    assert(charge_manager_step(&h,&o,&r)==CHARGE_DETACHED && !m.enabled);
    o=obs(0x26,0x40,0,9000); o.snapshot_age_ms=501;
    assert(charge_manager_step(&h,&o,&r)==CHARGE_IO_ERROR && !m.enabled);

    const char fail_ops[]={'e','L','I','C','l','E'};
    for (unsigned i=0;i<sizeof(fail_ops);++i) {
        mock f={.fail_on=fail_ops[i]}; charge_hal fh=make_hal(&f); charge_report fr={0};
        charge_observation fo=obs(0x26,0x40,0,9000);
        charge_state fs=charge_manager_step(&fh,&fo,&fr);
        assert(fs==CHARGE_IO_ERROR && !fr.active && !f.enabled);
    }
    printf("PASS: source decoding, 2A ceiling/margin, BQ IINDPM quantization, fail-closed sequencing, and 6 write-failure paths.\n");
    return 0;
}
