-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-- Migration 05 — Schema Enhancements for Expanded Ingestion
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-- Adds columns and tables to support:
--   • Normalized OBD scalar fields (from /pushobd JSONB payload)
--   • Alarm enrichment (severity, geofence context, acknowledgement)
--   • Vehicle enrichment (category, cost centre, depot location)
--   • New webhook endpoints: /pushevent, /pushoil, /pushtem, /pushlbs
--   • Geofence definition storage
--   • dwh_gold fact table expansion for full daily KPI reporting
--
-- Run after migration 04. Safe to re-run (uses IF NOT EXISTS / DO NOTHING).
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

BEGIN;

-- ── 1. Normalize OBD scalar fields ───────────────────────────────────────────
-- These are extracted from the obd_data JSONB column during /pushobd ingestion.
-- Raw JSONB is retained for full fidelity. Common OBD PID values only.

ALTER TABLE tracksolid.obd_readings
  ADD COLUMN IF NOT EXISTS engine_rpm       INTEGER,
  ADD COLUMN IF NOT EXISTS coolant_temp_c   NUMERIC(6,2),
  ADD COLUMN IF NOT EXISTS fuel_level_pct   NUMERIC(5,2),
  ADD COLUMN IF NOT EXISTS battery_voltage  NUMERIC(5,2),
  ADD COLUMN IF NOT EXISTS intake_pressure  NUMERIC(6,2),
  ADD COLUMN IF NOT EXISTS throttle_pct     NUMERIC(5,2),
  ADD COLUMN IF NOT EXISTS vehicle_speed    NUMERIC(7,2),
  ADD COLUMN IF NOT EXISTS engine_load_pct  NUMERIC(5,2);

COMMENT ON COLUMN tracksolid.obd_readings.engine_rpm      IS 'Engine RPM from OBD PID 0x0C';
COMMENT ON COLUMN tracksolid.obd_readings.coolant_temp_c  IS 'Coolant temperature °C from OBD PID 0x05';
COMMENT ON COLUMN tracksolid.obd_readings.fuel_level_pct  IS 'Fuel tank level % from OBD PID 0x2F';
COMMENT ON COLUMN tracksolid.obd_readings.battery_voltage IS 'Battery voltage (V) from OBD PID 0x42';
COMMENT ON COLUMN tracksolid.obd_readings.intake_pressure IS 'Intake manifold pressure kPa from OBD PID 0x0B';
COMMENT ON COLUMN tracksolid.obd_readings.throttle_pct    IS 'Throttle position % from OBD PID 0x11';
COMMENT ON COLUMN tracksolid.obd_readings.vehicle_speed   IS 'Vehicle speed km/h from OBD PID 0x0D';
COMMENT ON COLUMN tracksolid.obd_readings.engine_load_pct IS 'Calculated engine load % from OBD PID 0x04';

-- ── 2. Alarm enrichment ───────────────────────────────────────────────────────

ALTER TABLE tracksolid.alarms
  ADD COLUMN IF NOT EXISTS severity        TEXT,
  ADD COLUMN IF NOT EXISTS geofence_id     TEXT,
  ADD COLUMN IF NOT EXISTS geofence_name   TEXT,
  ADD COLUMN IF NOT EXISTS acknowledged_at TIMESTAMPTZ,
  ADD COLUMN IF NOT EXISTS acknowledged_by TEXT;

COMMENT ON COLUMN tracksolid.alarms.severity        IS 'Alarm severity level: critical | warning | info';
COMMENT ON COLUMN tracksolid.alarms.geofence_id     IS 'Tracksolid geofence ID if this is a geofence alarm';
COMMENT ON COLUMN tracksolid.alarms.geofence_name   IS 'Human-readable geofence name';
COMMENT ON COLUMN tracksolid.alarms.acknowledged_at IS 'Timestamp when alarm was acknowledged by an operator';
COMMENT ON COLUMN tracksolid.alarms.acknowledged_by IS 'Username or ID of operator who acknowledged the alarm';

-- ── 3. Vehicle enrichment ─────────────────────────────────────────────────────

ALTER TABLE tracksolid.devices
  ADD COLUMN IF NOT EXISTS vehicle_category TEXT,
  ADD COLUMN IF NOT EXISTS cost_centre      TEXT,
  ADD COLUMN IF NOT EXISTS assigned_route   TEXT,
  ADD COLUMN IF NOT EXISTS depot_geom       geometry(Point,4326),
  ADD COLUMN IF NOT EXISTS depot_address    TEXT;

COMMENT ON COLUMN tracksolid.devices.vehicle_category IS 'Vehicle type: truck | van | motorcycle | car | other';
COMMENT ON COLUMN tracksolid.devices.cost_centre      IS 'Business unit or department this vehicle belongs to';
COMMENT ON COLUMN tracksolid.devices.assigned_route   IS 'Regular route name or ID for route-based reporting';
COMMENT ON COLUMN tracksolid.devices.depot_geom       IS 'Home base/depot coordinates (WGS84)';
COMMENT ON COLUMN tracksolid.devices.depot_address    IS 'Human-readable depot address';

-- ── 4. Device login/logout events (webhook /pushevent) ────────────────────────

CREATE TABLE IF NOT EXISTS tracksolid.device_events (
  id          BIGSERIAL    PRIMARY KEY,
  imei        TEXT         NOT NULL REFERENCES tracksolid.devices(imei),
  event_type  TEXT         NOT NULL,  -- 'LOGIN' | 'LOGOUT'
  event_time  TIMESTAMPTZ  NOT NULL,
  timezone    TEXT,
  created_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
  UNIQUE (imei, event_type, event_time)
);

CREATE INDEX IF NOT EXISTS idx_device_events_imei_time
  ON tracksolid.device_events (imei, event_time DESC);

COMMENT ON TABLE tracksolid.device_events
  IS 'Device network connection and disconnection events from /pushevent webhook.';
COMMENT ON COLUMN tracksolid.device_events.event_type
  IS 'LOGIN = device connected to network; LOGOUT = device disconnected';

-- ── 5. Fuel sensor readings (webhook /pushoil) — hypertable ──────────────────

CREATE TABLE IF NOT EXISTS tracksolid.fuel_readings (
  imei         TEXT             NOT NULL REFERENCES tracksolid.devices(imei),
  reading_time TIMESTAMPTZ      NOT NULL,
  sensor_path  TEXT,
  value        NUMERIC(10,3),
  unit         TEXT,
  lat          DOUBLE PRECISION,
  lng          DOUBLE PRECISION,
  geom         geometry(Point,4326),
  created_at   TIMESTAMPTZ      NOT NULL DEFAULT now(),
  PRIMARY KEY (imei, reading_time)
);

COMMENT ON TABLE tracksolid.fuel_readings
  IS 'Fuel/oil sensor readings from /pushoil webhook. Unit varies per sensor: cm | % | V | L.';
COMMENT ON COLUMN tracksolid.fuel_readings.sensor_path
  IS 'Sensor channel identifier from the device (path field in API payload)';
COMMENT ON COLUMN tracksolid.fuel_readings.unit
  IS 'Measurement unit: cm (tank depth), % (percentage), V (voltage), L (litres)';

SELECT create_hypertable(
  'tracksolid.fuel_readings', 'reading_time',
  chunk_time_interval => INTERVAL '7 days',
  if_not_exists => TRUE
);

-- ── 6. Temperature & humidity readings (webhook /pushtem) — hypertable ────────

CREATE TABLE IF NOT EXISTS tracksolid.temperature_readings (
  imei         TEXT             NOT NULL REFERENCES tracksolid.devices(imei),
  reading_time TIMESTAMPTZ      NOT NULL,
  temperature  NUMERIC(6,2),
  humidity_pct NUMERIC(5,2),
  created_at   TIMESTAMPTZ      NOT NULL DEFAULT now(),
  PRIMARY KEY (imei, reading_time)
);

COMMENT ON TABLE tracksolid.temperature_readings
  IS 'Temperature and humidity sensor readings from /pushtem webhook. For cold-chain / refrigerated cargo monitoring.';

SELECT create_hypertable(
  'tracksolid.temperature_readings', 'reading_time',
  chunk_time_interval => INTERVAL '7 days',
  if_not_exists => TRUE
);

-- ── 7. LBS / cell-tower fallback positions (webhook /pushlbs) ────────────────

CREATE TABLE IF NOT EXISTS tracksolid.lbs_readings (
  id         BIGSERIAL    PRIMARY KEY,
  imei       TEXT         NOT NULL REFERENCES tracksolid.devices(imei),
  gate_time  TIMESTAMPTZ  NOT NULL,
  post_type  TEXT,
  lbs_data   JSONB,
  created_at TIMESTAMPTZ  NOT NULL DEFAULT now(),
  UNIQUE (imei, gate_time)
);

CREATE INDEX IF NOT EXISTS idx_lbs_readings_imei_time
  ON tracksolid.lbs_readings (imei, gate_time DESC);

COMMENT ON TABLE tracksolid.lbs_readings
  IS 'Cell tower / WiFi positioning fallback data from /pushlbs webhook. Used when GPS signal is unavailable.';
COMMENT ON COLUMN tracksolid.lbs_readings.post_type
  IS 'Positioning technology: WIFI | LBS (cell tower)';
COMMENT ON COLUMN tracksolid.lbs_readings.lbs_data
  IS 'Raw JSON payload containing MCC, MNC, and cell tower list for approximate geocoding.';

-- ── 8. Geofence definitions ───────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS tracksolid.geofences (
  id           BIGSERIAL    PRIMARY KEY,
  fence_id     TEXT         UNIQUE,
  fence_name   TEXT         NOT NULL,
  fence_type   TEXT,
  geom         geometry(Geometry,4326),
  radius_m     NUMERIC(10,2),
  description  TEXT,
  created_at   TIMESTAMPTZ  NOT NULL DEFAULT now(),
  updated_at   TIMESTAMPTZ  NOT NULL DEFAULT now()
);

COMMENT ON TABLE tracksolid.geofences
  IS 'Geofence boundary definitions synced from the Tracksolid platform.';
COMMENT ON COLUMN tracksolid.geofences.fence_type
  IS 'circle | polygon';
COMMENT ON COLUMN tracksolid.geofences.radius_m
  IS 'Radius in metres — only applicable for circle type geofences';

-- ── 9. Expand dwh_gold.fact_daily_fleet_metrics ───────────────────────────────

ALTER TABLE dwh_gold.fact_daily_fleet_metrics
  ADD COLUMN IF NOT EXISTS total_distance_km  NUMERIC(12,3),
  ADD COLUMN IF NOT EXISTS total_trips        INTEGER,
  ADD COLUMN IF NOT EXISTS total_drive_hours  NUMERIC(8,2),
  ADD COLUMN IF NOT EXISTS total_idle_hours   NUMERIC(8,2),
  ADD COLUMN IF NOT EXISTS fuel_consumed_l    NUMERIC(10,3),
  ADD COLUMN IF NOT EXISTS alarm_count        INTEGER,
  ADD COLUMN IF NOT EXISTS overspeed_count    INTEGER,
  ADD COLUMN IF NOT EXISTS day_start_time     TIME,
  ADD COLUMN IF NOT EXISTS day_end_time       TIME,
  ADD COLUMN IF NOT EXISTS avg_speed_kmh      NUMERIC(7,2),
  ADD COLUMN IF NOT EXISTS peak_speed_kmh     NUMERIC(7,2);

COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.total_distance_km IS 'Total km driven that day across all trips';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.total_trips       IS 'Number of completed trips';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.total_drive_hours IS 'Total hours of active driving (engine on + moving)';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.total_idle_hours  IS 'Total hours engine on but stationary';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.fuel_consumed_l   IS 'Total fuel consumed in litres (from webhook trip reports)';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.alarm_count       IS 'Total alarm events triggered that day';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.overspeed_count   IS 'Number of overspeed alarm events';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.day_start_time    IS 'Time of first trip start (Africa/Nairobi)';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.day_end_time      IS 'Time of last trip end (Africa/Nairobi)';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.avg_speed_kmh     IS 'Fleet average speed across all trips that day';
COMMENT ON COLUMN dwh_gold.fact_daily_fleet_metrics.peak_speed_kmh    IS 'Highest max_speed_kmh recorded across all trips';

-- ── 10. ETL function — refresh daily metrics ──────────────────────────────────
-- Populates dwh_gold.fact_daily_fleet_metrics for a given date.
-- Call nightly: SELECT dwh_gold.refresh_daily_metrics(CURRENT_DATE - 1);

CREATE OR REPLACE FUNCTION dwh_gold.refresh_daily_metrics(target_date DATE)
RETURNS void LANGUAGE plpgsql AS $$
BEGIN
  INSERT INTO dwh_gold.fact_daily_fleet_metrics (
    day,
    vehicle_key,
    total_distance_km,
    total_trips,
    total_drive_hours,
    total_idle_hours,
    fuel_consumed_l,
    alarm_count,
    overspeed_count,
    day_start_time,
    day_end_time,
    avg_speed_kmh,
    peak_speed_kmh
  )
  SELECT
    target_date                                                           AS day,
    t.imei                                                                AS vehicle_key,
    ROUND(SUM(t.distance_km)::numeric, 3)                                 AS total_distance_km,
    COUNT(*)                                                              AS total_trips,
    ROUND((SUM(t.driving_time_s) / 3600.0)::numeric, 2)                  AS total_drive_hours,
    ROUND((SUM(t.idle_time_s)    / 3600.0)::numeric, 2)                  AS total_idle_hours,
    ROUND(SUM(t.fuel_consumed_l)::numeric, 3)                            AS fuel_consumed_l,
    COUNT(a.id)                                                           AS alarm_count,
    COUNT(a.id) FILTER (WHERE a.alarm_type ILIKE '%speed%')              AS overspeed_count,
    MIN(t.start_time AT TIME ZONE 'Africa/Nairobi')::TIME                AS day_start_time,
    MAX(t.end_time   AT TIME ZONE 'Africa/Nairobi')::TIME                AS day_end_time,
    ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                              AS avg_speed_kmh,
    MAX(t.max_speed_kmh)                                                  AS peak_speed_kmh
  FROM tracksolid.trips t
  LEFT JOIN tracksolid.alarms a
    ON  a.imei = t.imei
    AND DATE(a.alarm_time AT TIME ZONE 'Africa/Nairobi') = target_date
  WHERE DATE(t.start_time AT TIME ZONE 'Africa/Nairobi') = target_date
    AND t.end_time IS NOT NULL
  GROUP BY t.imei
  ON CONFLICT (day, vehicle_key) DO UPDATE SET
    total_distance_km = EXCLUDED.total_distance_km,
    total_trips       = EXCLUDED.total_trips,
    total_drive_hours = EXCLUDED.total_drive_hours,
    total_idle_hours  = EXCLUDED.total_idle_hours,
    fuel_consumed_l   = EXCLUDED.fuel_consumed_l,
    alarm_count       = EXCLUDED.alarm_count,
    overspeed_count   = EXCLUDED.overspeed_count,
    day_start_time    = EXCLUDED.day_start_time,
    day_end_time      = EXCLUDED.day_end_time,
    avg_speed_kmh     = EXCLUDED.avg_speed_kmh,
    peak_speed_kmh    = EXCLUDED.peak_speed_kmh;
END;
$$;

COMMENT ON FUNCTION dwh_gold.refresh_daily_metrics(DATE)
  IS 'Populates or refreshes fact_daily_fleet_metrics for the given date. '
     'Call nightly: SELECT dwh_gold.refresh_daily_metrics(CURRENT_DATE - 1);';

COMMIT;
