-- EV spotřební forecast (SQL-first): vložení pozorování, párování jízd,
-- statistiky per DOW, odvození target SoC a očekávaného odjezdu.

create or replace function ems.fn_ev_vehicle_obs_insert(
  p_site_id int,
  p_vehicle_id int,
  p_trigger text,
  p_odometer_km numeric,
  p_soc_pct numeric,
  p_charging_state text
)
returns bigint
language sql
as $fn$
  insert into ems.ev_vehicle_obs (
    site_id, vehicle_id, trigger, odometer_km, soc_pct, charging_state
  )
  values (
    p_site_id, p_vehicle_id, p_trigger, p_odometer_km, p_soc_pct, p_charging_state
  )
  returning id;
$fn$;

-- Spáruje odjezdy s nejbližším následujícím příjezdem téhož vozidla.
create or replace function ems.fn_ev_build_trips()
returns int
language plpgsql
as $fn$
declare
  v_count int := 0;
  r record;
  v_arr record;
begin
  for r in
    select o.*
    from ems.ev_vehicle_obs o
    where o.trigger = 'departure'
      and o.odometer_km is not null
      and not exists (
        select 1 from ems.ev_trip t where t.departure_obs_id = o.id
      )
    order by o.vehicle_id, o.observed_at
  loop
    select a.* into v_arr
    from ems.ev_vehicle_obs a
    where a.vehicle_id = r.vehicle_id
      and a.trigger = 'arrival'
      and a.observed_at > r.observed_at
      and a.odometer_km is not null
    order by a.observed_at
    limit 1;

    if v_arr.id is null then
      continue;  -- jízda ještě neskončila
    end if;

    insert into ems.ev_trip (
      vehicle_id, departure_obs_id, arrival_obs_id,
      departed_at, arrived_at, km, kwh_est, charged_away
    )
    select
      r.vehicle_id, r.id, v_arr.id,
      r.observed_at, v_arr.observed_at,
      greatest(0, v_arr.odometer_km - r.odometer_km),
      case
        when r.soc_pct is null or v_arr.soc_pct is null then null
        when v_arr.soc_pct > r.soc_pct then null  -- nabíjeno cestou
        else round(((r.soc_pct - v_arr.soc_pct) / 100.0
                    * av.battery_capacity_kwh)::numeric, 2)
      end,
      coalesce(v_arr.soc_pct > r.soc_pct, false)
    from ems.asset_vehicle av
    where av.id = r.vehicle_id
    on conflict (departure_obs_id) do nothing;

    v_count := v_count + 1;
  end loop;
  return v_count;
end;
$fn$;

-- Přepočet týdenního rytmu z jízd za lookback okno (plný přepočet, ne EMA —
-- rebuild-friendly; jízdy s nabíjením cestou se počítají do km, ne do kWh).
create or replace function ems.fn_update_ev_usage_stats(
  p_lookback_days int default 60
)
returns int
language plpgsql
as $fn$
declare
  v_built int;
  v_count int;
begin
  v_built := ems.fn_ev_build_trips();

  with daily as (
    select
      t.vehicle_id,
      (t.departed_at at time zone 'Europe/Prague')::date as d,
      extract(dow from t.departed_at at time zone 'Europe/Prague')::int as dow,
      sum(t.kwh_est) filter (where not t.charged_away) as day_kwh,
      sum(t.km) as day_km,
      min(extract(hour from t.departed_at at time zone 'Europe/Prague')
          + extract(minute from t.departed_at at time zone 'Europe/Prague') / 60.0
      ) as first_departure_hour
    from ems.ev_trip t
    where t.departed_at >= now() - make_interval(days => p_lookback_days)
      and t.km >= 1.0  -- přepojení kabelu bez jízdy nepočítat
    group by 1, 2, 3
  ),
  agg as (
    select
      vehicle_id, dow,
      avg(day_kwh) as avg_kwh,
      stddev(day_kwh) as sd_kwh,
      avg(day_km) as avg_km,
      avg(first_departure_hour) as avg_dep,
      count(*) as samples
    from daily
    group by 1, 2
  )
  insert into ems.ev_usage_stats (
    vehicle_id, day_of_week, avg_day_kwh, stddev_day_kwh,
    avg_day_km, avg_departure_hour, sample_count, last_updated
  )
  select
    vehicle_id, dow, round(avg_kwh::numeric, 2), round(coalesce(sd_kwh, 0)::numeric, 2),
    round(avg_km::numeric, 1), round(avg_dep::numeric, 2), samples, now()
  from agg
  on conflict (vehicle_id, day_of_week) do update set
    avg_day_kwh = excluded.avg_day_kwh,
    stddev_day_kwh = excluded.stddev_day_kwh,
    avg_day_km = excluded.avg_day_km,
    avg_departure_hour = excluded.avg_departure_hour,
    sample_count = excluded.sample_count,
    last_updated = now();

  get diagnostics v_count = row_count;
  return v_count;
end;
$fn$;

comment on function ems.fn_update_ev_usage_stats is
'Spáruje nové jízdy (fn_ev_build_trips) a přepočte ev_usage_stats za lookback okno. Job 00:50.';

-- Příští očekávaný den s jízdou (>= 3 km průměru a >= 4 vzorky) v horizontu 7 dní.
create or replace function ems.fn_ev_next_departure(
  p_vehicle_id int,
  p_from timestamptz default now()
)
returns timestamptz
language sql
stable
as $fn$
  select min(dep)
  from (
    select (
      ((p_from at time zone 'Europe/Prague')::date + offs)::timestamp
      + make_interval(mins => (round(s.avg_departure_hour * 60))::int)
    ) at time zone 'Europe/Prague' as dep
    from generate_series(0, 7) offs
    join ems.ev_usage_stats s
      on s.vehicle_id = p_vehicle_id
      and s.day_of_week = extract(
        dow from (p_from at time zone 'Europe/Prague')::date + offs
      )::int
    where s.sample_count >= 4
      and s.avg_day_km >= 3.0
  ) cand
  where cand.dep > p_from + interval '30 minutes';
$fn$;

comment on function ems.fn_ev_next_departure is
'Nejbližší typický odjezd vozidla dle ev_usage_stats (DOW s >=4 vzorky a >=3 km). NULL = málo dat → volající použije default_deadline_hour.';

-- Target SoC pro odjezd v daný den: P80 denní spotřeby + rezerva 10 p.b.,
-- clamp do [min_target_soc_pct, 100]. NULL = málo dat.
create or replace function ems.fn_ev_required_soc(
  p_vehicle_id int,
  p_departure_at timestamptz
)
returns numeric
language sql
stable
as $fn$
  select least(100.0, greatest(
    av.min_target_soc_pct,
    ceil(
      (s.avg_day_kwh + 0.8416 * s.stddev_day_kwh)  -- ~P80
      / nullif(av.battery_capacity_kwh, 0) * 100.0
    ) + 10.0
  ))
  from ems.asset_vehicle av
  join ems.ev_usage_stats s
    on s.vehicle_id = av.id
    and s.day_of_week = extract(
      dow from p_departure_at at time zone 'Europe/Prague'
    )::int
  where av.id = p_vehicle_id
    and s.sample_count >= 4
    and s.avg_day_kwh is not null;
$fn$;

comment on function ems.fn_ev_required_soc is
'Cílové SoC (%) pro odjezd: P80 spotřeby dne v týdnu + 10 p.b. rezerva, mez [min_target_soc_pct, 100]. NULL = málo dat → default_target_soc_pct.';
