<?php
require_once __DIR__ . '/_db.php';
require_once __DIR__ . '/_config.php';
require_once __DIR__ . '/_utils.php';


function send_alert_mail($to, $subject, $body){
  global $MAIL_ENABLED, $MAIL_FROM;
  if(!$MAIL_ENABLED) return false;
  if(!$to) return false;
  $headers = "MIME-Version: 1.0\r\n";
  $headers .= "Content-type:text/plain; charset=UTF-8\r\n";
  $headers .= "From: {$MAIL_FROM}\r\n";
  // suppress warnings on some hosts
  return @mail($to, $subject, $body, $headers);
}

if(!$pdo) json_out(['ok'=>false,'error'=>'db_down'], 503);

$data = require_api_key($API_KEY);

// heartbeat
$hb = $data['heartbeat'] ?? null;
if (is_array($hb)) {
  $collector_id = trim((string)($hb['collector_id'] ?? ''));
  if ($collector_id !== '') {
    $stHb = $pdo->prepare("
      INSERT INTO collector_heartbeat (collector_id, host_name, ip_public, version, last_seen, last_ok, last_error)
      VALUES (:collector_id, :host_name, :ip_public, :version, :last_seen, :last_ok, :last_error)
      ON DUPLICATE KEY UPDATE
        host_name=VALUES(host_name),
        ip_public=VALUES(ip_public),
        version=VALUES(version),
        last_seen=VALUES(last_seen),
        last_ok=VALUES(last_ok),
        last_error=VALUES(last_error)
    ");
    $stHb->execute([
      ':collector_id'=>$collector_id,
      ':host_name'=>trim((string)($hb['host_name'] ?? '')),
      ':ip_public'=>trim((string)($hb['ip_public'] ?? '')),
      ':version'=>trim((string)($hb['version'] ?? '')),
      ':last_seen'=>now_str(),
      ':last_ok'=>(int)($hb['last_ok'] ?? 0),
      ':last_error'=>trim((string)($hb['last_error'] ?? '')),
    ]);
  }
}

$items = $data['items'] ?? [];
if (!is_array($items) || count($items) < 1) json_out(['ok'=>true,'saved'=>0,'errors'=>[['error'=>'no_items']]]);

$nowStr = now_str();

$stFindMeter = $pdo->prepare("SELECT id FROM meters WHERE ip=? LIMIT 1");
$stUpsertMeter = $pdo->prepare("
  INSERT INTO meters (name, ip, port, unit_id, byte_order, word_order, base_offset, is_active)
  VALUES (:name, :ip, :port, :unit_id, :byte_order, :word_order, :base_offset, 1)
  ON DUPLICATE KEY UPDATE
    name=VALUES(name),
    port=VALUES(port),
    unit_id=COALESCE(VALUES(unit_id), meters.unit_id),
    byte_order=VALUES(byte_order),
    word_order=VALUES(word_order),
    base_offset=VALUES(base_offset),
    is_active=1
");

$stUpsertLast = $pdo->prepare("
  INSERT INTO meter_last
  (meter_id, ts, online, rtt_ms, last_error,
   freq_hz,
   vln1_v, vln2_v, vln3_v,
   vll1_v, vll2_v, vll3_v,
   il1_a, il2_a, il3_a, iln_a,
   p_total_kw, p_l1_kw, p_l2_kw, p_l3_kw,
   q_total_kvar, s_total_kva,
   pf, pf_l1, pf_l2, pf_l3,
   kwh_import_total, kwh_export_total,
   kwh_t1, kwh_t2, kwh_t3, kwh_t4, kwh_t5, kwh_t6, kwh_t7, kwh_t8,
   alarm_mask, status_mask)
  VALUES
  (:meter_id, :ts, :online, :rtt_ms, :last_error,
   :freq_hz,
   :vln1_v, :vln2_v, :vln3_v,
   :vll1_v, :vll2_v, :vll3_v,
   :il1_a, :il2_a, :il3_a, :iln_a,
   :p_total_kw, :p_l1_kw, :p_l2_kw, :p_l3_kw,
   :q_total_kvar, :s_total_kva,
   :pf, :pf_l1, :pf_l2, :pf_l3,
   :kwh_import_total, :kwh_export_total,
   :kwh_t1, :kwh_t2, :kwh_t3, :kwh_t4, :kwh_t5, :kwh_t6, :kwh_t7, :kwh_t8,
   :alarm_mask, :status_mask)
  ON DUPLICATE KEY UPDATE
    ts=VALUES(ts),
    online=VALUES(online),
    rtt_ms=VALUES(rtt_ms),
    last_error=VALUES(last_error),

    freq_hz=VALUES(freq_hz),

    vln1_v=VALUES(vln1_v),
    vln2_v=VALUES(vln2_v),
    vln3_v=VALUES(vln3_v),
    vll1_v=VALUES(vll1_v),
    vll2_v=VALUES(vll2_v),
    vll3_v=VALUES(vll3_v),
    il1_a=VALUES(il1_a),
    il2_a=VALUES(il2_a),
    il3_a=VALUES(il3_a),
    iln_a=VALUES(iln_a),

    p_total_kw=VALUES(p_total_kw),
    p_l1_kw=VALUES(p_l1_kw),
    p_l2_kw=VALUES(p_l2_kw),
    p_l3_kw=VALUES(p_l3_kw),
    q_total_kvar=VALUES(q_total_kvar),
    s_total_kva=VALUES(s_total_kva),

    pf=VALUES(pf),
    pf_l1=VALUES(pf_l1),
    pf_l2=VALUES(pf_l2),
    pf_l3=VALUES(pf_l3),

    kwh_import_total=COALESCE(VALUES(kwh_import_total), meter_last.kwh_import_total),
    kwh_export_total=COALESCE(VALUES(kwh_export_total), meter_last.kwh_export_total),
    kwh_t1=COALESCE(VALUES(kwh_t1), meter_last.kwh_t1),
    kwh_t2=COALESCE(VALUES(kwh_t2), meter_last.kwh_t2),
    kwh_t3=COALESCE(VALUES(kwh_t3), meter_last.kwh_t3),
    kwh_t4=COALESCE(VALUES(kwh_t4), meter_last.kwh_t4),
    kwh_t5=COALESCE(VALUES(kwh_t5), meter_last.kwh_t5),
    kwh_t6=COALESCE(VALUES(kwh_t6), meter_last.kwh_t6),
    kwh_t7=COALESCE(VALUES(kwh_t7), meter_last.kwh_t7),
    kwh_t8=COALESCE(VALUES(kwh_t8), meter_last.kwh_t8),

    alarm_mask=VALUES(alarm_mask),
    status_mask=VALUES(status_mask)
");

$stInsertEnergy = $pdo->prepare("
  INSERT INTO meter_energy
  (meter_id, ts, kwh_import_total, kwh_export_total, kwh_t1, kwh_t2, kwh_t3, kwh_t4, kwh_t5, kwh_t6, kwh_t7, kwh_t8)
  VALUES
  (:meter_id, :ts, :kwh_import_total, :kwh_export_total, :kwh_t1, :kwh_t2, :kwh_t3, :kwh_t4, :kwh_t5, :kwh_t6, :kwh_t7, :kwh_t8)
");

$stUpdLastEnergyTs = $pdo->prepare("UPDATE meter_last SET last_energy_ts=? WHERE meter_id=?");


$stInsertHist = $pdo->prepare("
  INSERT INTO meter_history
  (meter_id, ts, online, last_error, freq_hz, vln1_v, vln2_v, vln3_v, il1_a, il2_a, il3_a, p_total_kw, pf)
  VALUES
  (:meter_id, :ts, :online, :last_error, :freq_hz, :vln1_v, :vln2_v, :vln3_v, :il1_a, :il2_a, :il3_a, :p_total_kw, :pf)
");

$stUpdLastHistTs = $pdo->prepare("UPDATE meter_last SET last_hist_ts=? WHERE meter_id=?");

$stGetLastHistTs = $pdo->prepare("SELECT last_hist_ts FROM meter_last WHERE meter_id=? LIMIT 1");

$stGetThresholds = $pdo->prepare("SELECT offline_after_sec, kw_peak_limit, pf_min, v_min, v_max, email_to, email_enabled FROM meter_thresholds WHERE meter_id=? LIMIT 1");
$stLastEvent = $pdo->prepare("SELECT ts, message FROM system_events WHERE source='METER' AND meter_id=? ORDER BY ts DESC LIMIT 1");

$okCount = 0;
$errItems = [];

$pdo->beginTransaction();
try{
  foreach($items as $idx=>$it){
    $ip = trim((string)($it['ip'] ?? ''));
    if ($ip === '') { $errItems[] = ['idx'=>$idx,'error'=>'missing_ip']; continue; }

    $name = trim((string)($it['name'] ?? $ip));
    $port = (int)($it['port'] ?? 502);
    $unit_id = (isset($it['unit_id']) ? (int)$it['unit_id'] : null);

    $byte_order = strtoupper((string)($it['byte_order'] ?? 'BIG'));
    $word_order = strtoupper((string)($it['word_order'] ?? 'BIG'));
    $base_offset = (int)($it['base_offset'] ?? 0);

    if (!in_array($byte_order, ['BIG','LITTLE'], true)) $byte_order = 'BIG';
    if (!in_array($word_order, ['BIG','LITTLE'], true)) $word_order = 'BIG';
    if (!in_array($base_offset, [0,1], true)) $base_offset = 0;

    $stUpsertMeter->execute([
      ':name'=>$name, ':ip'=>$ip, ':port'=>$port, ':unit_id'=>$unit_id,
      ':byte_order'=>$byte_order, ':word_order'=>$word_order, ':base_offset'=>$base_offset
    ]);

    $stFindMeter->execute([$ip]);
    $row = $stFindMeter->fetch();
    if(!$row){ $errItems[]=['idx'=>$idx,'ip'=>$ip,'error'=>'meter_not_found']; continue; }
    $meter_id = (int)$row['id'];

    $online = (int)($it['online'] ?? 0);
    $rtt_ms = (isset($it['rtt_ms']) ? (int)$it['rtt_ms'] : null);
    $last_error = trim((string)($it['last_error'] ?? ''));

    $m = (array)($it['m'] ?? []);
    $e = (array)($it['e'] ?? []);

    $alarm_mask = isset($it['alarm_mask']) ? u64_or_null($it['alarm_mask']) : null;
    $status_mask = isset($it['status_mask']) ? u64_or_null($it['status_mask']) : null;

    $stUpsertLast->execute([
      ':meter_id'=>$meter_id,
      ':ts'=>$nowStr,
      ':online'=>$online,
      ':rtt_ms'=>$rtt_ms,
      ':last_error'=>($last_error !== '' ? mb_substr($last_error,0,255,'UTF-8') : null),

      ':freq_hz'=>num_or_null($m['freq_hz'] ?? null),

      ':vln1_v'=>num_or_null($m['vln1_v'] ?? null),
      ':vln2_v'=>num_or_null($m['vln2_v'] ?? null),
      ':vln3_v'=>num_or_null($m['vln3_v'] ?? null),

      ':vll1_v'=>num_or_null($m['vll1_v'] ?? null),
      ':vll2_v'=>num_or_null($m['vll2_v'] ?? null),
      ':vll3_v'=>num_or_null($m['vll3_v'] ?? null),

      ':il1_a'=>num_or_null($m['il1_a'] ?? null),
      ':il2_a'=>num_or_null($m['il2_a'] ?? null),
      ':il3_a'=>num_or_null($m['il3_a'] ?? null),
      ':iln_a'=>num_or_null($m['iln_a'] ?? null),

      ':p_total_kw'=>num_or_null($m['p_total_kw'] ?? null),
      ':p_l1_kw'=>num_or_null($m['p_l1_kw'] ?? null),
      ':p_l2_kw'=>num_or_null($m['p_l2_kw'] ?? null),
      ':p_l3_kw'=>num_or_null($m['p_l3_kw'] ?? null),

      ':q_total_kvar'=>num_or_null($m['q_total_kvar'] ?? null),
      ':s_total_kva'=>num_or_null($m['s_total_kva'] ?? null),

      ':pf'=>num_or_null($m['pf'] ?? null),
      ':pf_l1'=>num_or_null($m['pf_l1'] ?? null),
      ':pf_l2'=>num_or_null($m['pf_l2'] ?? null),
      ':pf_l3'=>num_or_null($m['pf_l3'] ?? null),

      ':kwh_import_total'=>num_or_null(($e['kwh_import_total'] ?? null)),
      ':kwh_export_total'=>num_or_null(($e['kwh_export_total'] ?? null)),
      ':kwh_t1'=>num_or_null(($e['kwh_t1'] ?? null)),
      ':kwh_t2'=>num_or_null(($e['kwh_t2'] ?? null)),
      ':kwh_t3'=>num_or_null(($e['kwh_t3'] ?? null)),
      ':kwh_t4'=>num_or_null(($e['kwh_t4'] ?? null)),
      ':kwh_t5'=>num_or_null(($e['kwh_t5'] ?? null)),
      ':kwh_t6'=>num_or_null(($e['kwh_t6'] ?? null)),
      ':kwh_t7'=>num_or_null(($e['kwh_t7'] ?? null)),
      ':kwh_t8'=>num_or_null(($e['kwh_t8'] ?? null)),

      ':alarm_mask'=>$alarm_mask,
      ':status_mask'=>$status_mask,
    
    ]);

    // thresholds + offline + basic alarms
    $stGetThresholds->execute([$meter_id]);
    $thr = $stGetThresholds->fetch(PDO::FETCH_ASSOC) ?: null;

    // Determine OFFLINE based on freshness (collector already sets online), but if threshold says otherwise, log if stale
    if ($thr && (int)$thr['offline_after_sec'] > 0) {
      // if last meter ts older than offline_after_sec -> treat as offline in UI (panel uses api/status.php too)
      // (Here we only log transitions based on collector online flag.)
    }

    // Insert history snapshot every N seconds (for reports/charts)
    $doHist = false;
    $interval = (int)($HISTORY_INSERT_EVERY_SEC ?? 5);
    if ($interval < 1) $interval = 5;
    $stGetLastHistTs->execute([$meter_id]);
    $rowHist = $stGetLastHistTs->fetch(PDO::FETCH_ASSOC);
    $lastHistTs = $rowHist && $rowHist['last_hist_ts'] ? strtotime($rowHist['last_hist_ts']) : 0;
    if (time() - $lastHistTs >= $interval) $doHist = true;

    if ($doHist) {
      $stInsertHist->execute([
        ':meter_id'=>$meter_id,
        ':ts'=>$nowStr,
        ':online'=>$online,
        ':last_error'=>($last_error !== '' ? mb_substr($last_error,0,255,'UTF-8') : null),
        ':freq_hz'=>num_or_null($m['freq_hz'] ?? null),
        ':vln1_v'=>num_or_null($m['vln1_v'] ?? null),
        ':vln2_v'=>num_or_null($m['vln2_v'] ?? null),
        ':vln3_v'=>num_or_null($m['vln3_v'] ?? null),
        ':il1_a'=>num_or_null($m['il1_a'] ?? null),
        ':il2_a'=>num_or_null($m['il2_a'] ?? null),
        ':il3_a'=>num_or_null($m['il3_a'] ?? null),
        ':p_total_kw'=>num_or_null($m['p_total_kw'] ?? null),
        ':pf'=>num_or_null($m['pf'] ?? null),
      ]);
      $stUpdLastHistTs->execute([$nowStr, $meter_id]);
    }

    // Peak / PF / Voltage alarms -> write to system_events (+ optional email)
    if ($thr && (int)$thr['email_enabled'] === 1) {
      $to = trim((string)$thr['email_to']);
      if ($to === '') $to = null;
    } else {
      $to = null;
    }

    // kW peak
    if ($thr && $online === 1 && $thr['kw_peak_limit'] !== null) {
      $lim = (float)$thr['kw_peak_limit'];
      $kw = (float)($m['p_total_kw'] ?? 0);
      if ($lim > 0 && $kw > $lim) {
        $msg = "kW limit aşıldı: {$kw} kW > {$lim} kW";
        event_insert($pdo, 'WARN', 'ALARM', $msg, $meter_id);
        if ($to) send_alert_mail($to, $MAIL_SUBJECT_PREFIX."Sayaç {$meter_id} kW Alarm", $msg);
      }
    }

    // PF min
    if ($thr && $online === 1 && $thr['pf_min'] !== null) {
      $pfm = (float)$thr['pf_min'];
      $pfv = (float)($m['pf'] ?? 1);
      if ($pfm > 0 && $pfv > 0 && $pfv < $pfm) {
        $msg = "Güç faktörü düşük: PF {$pfv} < {$pfm}";
        event_insert($pdo, 'WARN', 'ALARM', $msg, $meter_id);
        if ($to) send_alert_mail($to, $MAIL_SUBJECT_PREFIX."Sayaç {$meter_id} PF Alarm", $msg);
      }
    }

    // Voltage bounds (phase-neutral)
    if ($thr && $online === 1 && ($thr['v_min'] !== null || $thr['v_max'] !== null)) {
      $vmin = ($thr['v_min'] !== null) ? (float)$thr['v_min'] : null;
      $vmax = ($thr['v_max'] !== null) ? (float)$thr['v_max'] : null;
      $vals = [
        (float)($m['vln1_v'] ?? 0),
        (float)($m['vln2_v'] ?? 0),
        (float)($m['vln3_v'] ?? 0),
      ];
      $low = $vmin !== null ? min($vals) < $vmin : false;
      $high = $vmax !== null ? max($vals) > $vmax : false;
      if ($low || $high) {
        $msg = "Voltaj sınır dışı: [".implode(', ', $vals)."] V";
        if ($vmin !== null) $msg .= " (min {$vmin})";
        if ($vmax !== null) $msg .= " (max {$vmax})";
        event_insert($pdo, 'WARN', 'ALARM', $msg, $meter_id);
        if ($to) send_alert_mail($to, $MAIL_SUBJECT_PREFIX."Sayaç {$meter_id} Voltaj Alarm", $msg);
      }
    }

    if ($online !== 1 && $last_error !== '') {
      $stLastEvent->execute([$meter_id]);
      $prev = $stLastEvent->fetch();
      $need = true;
      if ($prev){
        $age = time() - strtotime($prev['ts']);
        if ($age < (int)$EVENT_THROTTLE_SEC && (string)$prev['message'] === (string)$last_error) $need = false;
      }
      if ($need) event_insert($pdo, 'WARN', 'METER', $last_error, $meter_id);
    }

    if (!empty($it['write_energy'])) {
      $stInsertEnergy->execute([
        ':meter_id'=>$meter_id,
        ':ts'=>$nowStr,
        ':kwh_import_total'=>num_or_null(($e['kwh_import_total'] ?? null)),
        ':kwh_export_total'=>num_or_null(($e['kwh_export_total'] ?? null)),
        ':kwh_t1'=>num_or_null(($e['kwh_t1'] ?? null)),
        ':kwh_t2'=>num_or_null(($e['kwh_t2'] ?? null)),
        ':kwh_t3'=>num_or_null(($e['kwh_t3'] ?? null)),
        ':kwh_t4'=>num_or_null(($e['kwh_t4'] ?? null)),
        ':kwh_t5'=>num_or_null(($e['kwh_t5'] ?? null)),
        ':kwh_t6'=>num_or_null(($e['kwh_t6'] ?? null)),
        ':kwh_t7'=>num_or_null(($e['kwh_t7'] ?? null)),
        ':kwh_t8'=>num_or_null(($e['kwh_t8'] ?? null)),
      ]);
      $stUpdLastEnergyTs->execute([$nowStr, $meter_id]);
    }

    $okCount++;
  }

  $pdo->commit();
}catch(Throwable $ex){
  $pdo->rollBack();
  json_out(['ok'=>false,'error'=>'db_write_failed','detail'=>$ex->getMessage()], 500);
}

json_out(['ok'=>true,'saved'=>$okCount,'errors'=>$errItems], 200);
