-- uložení planning_run + planning_interval v jedné transakci

create or replace function ems.fn_planning_run_commit(
  p_site_id int,
  p_horizon_start timestamptz,
  p_horizon_end timestamptz,
  p_run_meta jsonb,
  p_intervals jsonb,
  p_activate_run boolean default true
)
returns int
language plpgsql
as $fn$
declare
  v_run_id int;
  r record;
  v_has_slot_inputs boolean;
begin
  v_has_slot_inputs := coalesce(
    (jsonb_typeof(p_intervals) = 'array' and jsonb_array_length(p_intervals) > 0
      and (p_intervals->0) ? 'load_baseline_w'),
    false
  );
  insert into ems.planning_run (
    site_id, horizon_start, horizon_end, status,
    run_type, triggered_by, replan_from,
    soc_at_replan_wh, solver_duration_ms, forecast_correction_factor,
    solver_params
  ) values (
    p_site_id,
    p_horizon_start,
    p_horizon_end,
    case when p_activate_run then 'draft' else 'comparison' end,
    nullif(trim(p_run_meta->>'run_type'), ''),
    nullif(trim(p_run_meta->>'triggered_by'), ''),
    case
      when p_run_meta ? 'replan_from' and (p_run_meta->>'replan_from') is not null
        and (p_run_meta->>'replan_from') <> 'null'
      then (p_run_meta->>'replan_from')::timestamptz
      else null::timestamptz
    end,
    (p_run_meta->>'soc_at_replan_wh')::numeric,
    (p_run_meta->>'solver_duration_ms')::int,
    (p_run_meta->>'forecast_correction_factor')::numeric,
    case
      when p_run_meta ? 'solver_params' and jsonb_typeof(p_run_meta->'solver_params') = 'object'
      then p_run_meta->'solver_params'
      else null::jsonb
    end
  )
  returning id into v_run_id;

  for r in select * from jsonb_array_elements(p_intervals) as elem(value)
  loop
    if v_has_slot_inputs then
      insert into ems.planning_interval (
        run_id, interval_start,
        battery_setpoint_w, battery_soc_target_pct,
        grid_setpoint_w,
        export_mode,
        export_limit_w,
        deye_physical_mode,
        deye_gen_cutoff_enabled,
        ev1_setpoint_w, ev2_setpoint_w, ev1_via_bat_w, ev2_via_bat_w,
        heat_pump_enabled, heat_pump_setpoint_w,
        pv_a_curtailed_w, expected_cost_czk,
        effective_buy_price, effective_sell_price,
        is_predicted_price,
        load_baseline_w,
        pv_a_forecast_raw_w, pv_b_forecast_raw_w,
        pv_a_forecast_solver_w, pv_b_forecast_solver_w,
        cashflow_czk,
        battery_arbitrage_czk,
        penalty_czk,
        green_bonus_czk
      ) values (
        v_run_id,
        (r.value->>'interval_start')::timestamptz,
        (r.value->>'battery_setpoint_w')::int,
        (r.value->>'battery_soc_target_pct')::numeric,
        (r.value->>'grid_setpoint_w')::int,
        nullif(trim(r.value->>'export_mode'), ''),
        nullif(r.value->>'export_limit_w', '')::int,
        nullif(trim(r.value->>'deye_physical_mode'), ''),
        (r.value->>'deye_gen_cutoff_enabled')::boolean,
        nullif(r.value->>'ev1_setpoint_w', '')::int,
        nullif(r.value->>'ev2_setpoint_w', '')::int,
        coalesce((r.value->>'ev1_via_bat_w')::int, 0),
        coalesce((r.value->>'ev2_via_bat_w')::int, 0),
        coalesce((r.value->>'heat_pump_enabled')::boolean, false),
        (r.value->>'heat_pump_setpoint_w')::int,
        (r.value->>'pv_a_curtailed_w')::int,
        (r.value->>'expected_cost_czk')::numeric,
        (r.value->>'effective_buy_price')::numeric,
        (r.value->>'effective_sell_price')::numeric,
        coalesce((r.value->>'is_predicted_price')::boolean, false),
        (r.value->>'load_baseline_w')::int,
        (r.value->>'pv_a_forecast_raw_w')::int,
        (r.value->>'pv_b_forecast_raw_w')::int,
        (r.value->>'pv_a_forecast_solver_w')::int,
        (r.value->>'pv_b_forecast_solver_w')::int,
        nullif(r.value->>'cashflow_czk', '')::numeric,
        nullif(r.value->>'battery_arbitrage_czk', '')::numeric,
        nullif(r.value->>'penalty_czk', '')::numeric,
        nullif(r.value->>'green_bonus_czk', '')::numeric
      );
    else
      insert into ems.planning_interval (
        run_id, interval_start,
        battery_setpoint_w, battery_soc_target_pct,
        grid_setpoint_w,
        export_mode,
        export_limit_w,
        deye_physical_mode,
        deye_gen_cutoff_enabled,
        ev1_setpoint_w, ev2_setpoint_w, ev1_via_bat_w, ev2_via_bat_w,
        heat_pump_enabled, heat_pump_setpoint_w,
        pv_a_curtailed_w, expected_cost_czk,
        effective_buy_price, effective_sell_price,
        is_predicted_price,
        cashflow_czk,
        battery_arbitrage_czk,
        penalty_czk,
        green_bonus_czk
      ) values (
        v_run_id,
        (r.value->>'interval_start')::timestamptz,
        (r.value->>'battery_setpoint_w')::int,
        (r.value->>'battery_soc_target_pct')::numeric,
        (r.value->>'grid_setpoint_w')::int,
        nullif(trim(r.value->>'export_mode'), ''),
        nullif(r.value->>'export_limit_w', '')::int,
        nullif(trim(r.value->>'deye_physical_mode'), ''),
        (r.value->>'deye_gen_cutoff_enabled')::boolean,
        nullif(r.value->>'ev1_setpoint_w', '')::int,
        nullif(r.value->>'ev2_setpoint_w', '')::int,
        coalesce((r.value->>'ev1_via_bat_w')::int, 0),
        coalesce((r.value->>'ev2_via_bat_w')::int, 0),
        coalesce((r.value->>'heat_pump_enabled')::boolean, false),
        (r.value->>'heat_pump_setpoint_w')::int,
        (r.value->>'pv_a_curtailed_w')::int,
        (r.value->>'expected_cost_czk')::numeric,
        (r.value->>'effective_buy_price')::numeric,
        (r.value->>'effective_sell_price')::numeric,
        coalesce((r.value->>'is_predicted_price')::boolean, false),
        nullif(r.value->>'cashflow_czk', '')::numeric,
        nullif(r.value->>'battery_arbitrage_czk', '')::numeric,
        nullif(r.value->>'penalty_czk', '')::numeric,
        nullif(r.value->>'green_bonus_czk', '')::numeric
      );
    end if;
  end loop;

  update ems.planning_run
  set status = 'superseded'
  where p_activate_run
    and site_id = p_site_id
    and status = 'active'
    and id <> v_run_id;

  if p_activate_run then
    update ems.planning_run
    set status = 'active'
    where id = v_run_id;
  end if;

  return v_run_id;
end;
$fn$;

create or replace function ems.fn_forecast_correction_log_insert(
  p_site_id int,
  p_window_start timestamptz,
  p_window_end timestamptz,
  p_actual_pv_wh numeric,
  p_forecast_pv_wh numeric,
  p_correction_factor numeric,
  p_applied_to_run_id int
)
returns void
language sql
as $fn$
  insert into ems.forecast_correction_log (
    site_id, window_start, window_end,
    actual_pv_wh, forecast_pv_wh, correction_factor, applied_to_run_id
  ) values (
    p_site_id, p_window_start, p_window_end,
    p_actual_pv_wh, p_forecast_pv_wh, p_correction_factor, p_applied_to_run_id
  );
$fn$;
