-- =============================================================
-- TRACKSOLID DWH SETUP & PERMISSIONS
-- Target Database: tracksolid_dwh
-- =============================================================

-- 1. EXTENSIONS
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE EXTENSION IF NOT EXISTS postgis; -- REQUIRED for geometry(Point,4326) columns

-- 2. ROLE CREATION (Idempotent)
-- SECURITY: Passwords below are placeholders. Before applying this file:
--   1. Generate two strong secrets (e.g. `openssl rand -hex 24`)
--   2. Replace both CHANGE_ME_BEFORE_APPLY tokens in-session (do NOT commit real values)
--   3. Store the generated secrets in the n8n / Grafana credential stores only
-- Rotation: `ALTER ROLE <role> PASSWORD '<new secret>'` as a superuser.
DO $$
BEGIN
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'dwh_owner') THEN
        CREATE ROLE dwh_owner WITH LOGIN PASSWORD 'CHANGE_ME_BEFORE_APPLY';
    END IF;
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'grafana_ro') THEN
        CREATE ROLE grafana_ro WITH LOGIN PASSWORD 'CHANGE_ME_BEFORE_APPLY';
    END IF;
END$$;

-- Grant database connection
GRANT CONNECT ON DATABASE tracksolid_dwh TO dwh_owner;
GRANT CONNECT ON DATABASE tracksolid_dwh TO grafana_ro;

-- 3. SCHEMAS
CREATE SCHEMA IF NOT EXISTS bronze AUTHORIZATION dwh_owner;
CREATE SCHEMA IF NOT EXISTS silver AUTHORIZATION dwh_owner;
CREATE SCHEMA IF NOT EXISTS gold AUTHORIZATION dwh_owner;

ALTER DATABASE tracksolid_dwh SET search_path TO bronze, silver, gold, public;

-- 4. PERMISSIONS & DEFAULT PRIVILEGES (Critical for Security & Automation)
-- Schema access
GRANT USAGE, CREATE ON SCHEMA bronze TO dwh_owner;
GRANT USAGE, CREATE ON SCHEMA silver TO dwh_owner;
GRANT USAGE, CREATE ON SCHEMA gold TO dwh_owner;
GRANT USAGE ON SCHEMA bronze TO grafana_ro;
GRANT USAGE ON SCHEMA silver TO grafana_ro;
GRANT USAGE ON SCHEMA gold TO grafana_ro;
GRANT USAGE ON SCHEMA public TO dwh_owner, grafana_ro;

-- Existing table access for Grafana
GRANT SELECT ON ALL TABLES IN SCHEMA bronze TO grafana_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA silver TO grafana_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA gold TO grafana_ro;

-- FUTURE table access: Any table created by dwh_owner will automatically be readable by grafana_ro
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA bronze GRANT SELECT ON TABLES TO grafana_ro;
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA silver GRANT SELECT ON TABLES TO grafana_ro;
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA gold GRANT SELECT ON TABLES TO grafana_ro;

-- 5. BRONZE SCHEMA TABLES
-- Run as dwh_owner to ensure correct ownership & default privileges apply
SET ROLE dwh_owner;
SET search_path TO bronze, public;

-- 5.1 DEVICES (Slowly Changing Dimension - Type 2 handled in Silver)
CREATE TABLE IF NOT EXISTS bronze.devices (
    imei TEXT PRIMARY KEY,
    device_name TEXT,
    mc_type TEXT,
    mc_type_use_scope TEXT,
    vehicle_name TEXT,
    vehicle_number TEXT,
    vehicle_models TEXT,
    vehicle_icon TEXT,
    vin TEXT,
    engine_number TEXT,
    vehicle_brand TEXT,
    fuel_100km NUMERIC(6,2),
    driver_name TEXT,
    driver_phone TEXT,
    sim TEXT,
    iccid TEXT,
    imsi TEXT,
    account TEXT,
    customer_name TEXT,
    device_group_id TEXT,
    device_group TEXT,
    activation_time TIMESTAMPTZ,
    expiration TIMESTAMPTZ,
    enabled_flag SMALLINT DEFAULT 1 NOT NULL,
    status TEXT DEFAULT 'active'::text,
    city TEXT,
    current_mileage_km NUMERIC(12,2),
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    last_synced_at TIMESTAMPTZ,
    vehicle_category TEXT,
    cost_centre TEXT,
    assigned_route TEXT,
    depot_geom geometry(Point,4326),
    depot_address TEXT,
    assigned_city TEXT,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.2 POSITION HISTORY (High-volume fact table)
CREATE TABLE IF NOT EXISTS bronze.position_history (
    imei TEXT NOT NULL,
    gps_time TIMESTAMPTZ NOT NULL,
    geom geometry(Point,4326),
    lat DOUBLE PRECISION,
    lng DOUBLE PRECISION,
    speed NUMERIC(7,2),
    direction NUMERIC(6,2),
    acc_status TEXT,
    satellite SMALLINT,
    current_mileage NUMERIC(12,2),
    recorded_at TIMESTAMPTZ DEFAULT now(),
    altitude NUMERIC(8,2),
    post_type SMALLINT,
    source TEXT DEFAULT 'poll'::text,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (imei, gps_time)
);

-- 5.3 TRIPS (Aggregated fact table)
CREATE TABLE IF NOT EXISTS bronze.trips (
    id BIGINT NOT NULL,
    imei TEXT NOT NULL,
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ,
    start_geom geometry(Point,4326),
    end_geom geometry(Point,4326),
    distance_km NUMERIC(12,2),
    avg_speed_kmh NUMERIC(7,2),
    max_speed_kmh NUMERIC(7,2),
    updated_at TIMESTAMPTZ DEFAULT now(),
    fuel_consumed_l NUMERIC(8,2),
    idle_time_s INTEGER,
    driving_time_s INTEGER,
    trip_seq INTEGER,
    source TEXT DEFAULT 'poll'::text,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (id)
);

-- 5.4 ALARMS (Event log fact table)
CREATE TABLE IF NOT EXISTS bronze.alarms (
    id BIGINT PRIMARY KEY,
    imei TEXT,
    alarm_type TEXT,
    alarm_time TIMESTAMPTZ,
    geom geometry(Point,4326),
    lat DOUBLE PRECISION,
    lng DOUBLE PRECISION,
    speed NUMERIC(7,2),
    acc_status TEXT,
    updated_at TIMESTAMPTZ DEFAULT now(),
    alarm_name TEXT,
    source TEXT DEFAULT 'poll'::text,
    severity TEXT,
    geofence_id TEXT,
    geofence_name TEXT,
    acknowledged_at TIMESTAMPTZ,
    acknowledged_by TEXT,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.5 DEVICE EVENTS (Connection lifecycle)
CREATE TABLE IF NOT EXISTS bronze.device_events (
    id BIGINT PRIMARY KEY,
    imei TEXT NOT NULL,
    event_type TEXT NOT NULL,
    event_time TIMESTAMPTZ NOT NULL,
    timezone TEXT,
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.6 DISPATCH LOG (Operational/SLA tracking)
CREATE TABLE IF NOT EXISTS bronze.dispatch_log (
    dispatch_id BIGINT PRIMARY KEY,
    ticket_id TEXT NOT NULL,
    imei TEXT NOT NULL,
    driver_name TEXT,
    job_lat DOUBLE PRECISION NOT NULL,
    job_lng DOUBLE PRECISION NOT NULL,
    job_geom geometry(Point,4326),
    assigned_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    first_movement_at TIMESTAMPTZ,
    on_site_at TIMESTAMPTZ,
    resolved_at TIMESTAMPTZ,
    cancelled_at TIMESTAMPTZ,
    distance_km NUMERIC(8,2),
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.7 FAULT CODES (OBD/DTC diagnostics)
CREATE TABLE IF NOT EXISTS bronze.fault_codes (
    id BIGINT PRIMARY KEY,
    imei TEXT NOT NULL,
    reported_at TIMESTAMPTZ NOT NULL,
    fault_code TEXT NOT NULL,
    status_flags INTEGER,
    lat DOUBLE PRECISION,
    lng DOUBLE PRECISION,
    geom geometry(Point,4326),
    event_time TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.8 FUEL READINGS
CREATE TABLE IF NOT EXISTS bronze.fuel_readings (
    imei TEXT NOT NULL,
    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 DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (imei, reading_time)
);

-- 5.9 GEOFENCES (Dimension/Reference)
CREATE TABLE IF NOT EXISTS bronze.geofences (
    id BIGINT PRIMARY KEY,
    fence_id TEXT,
    fence_name TEXT NOT NULL,
    fence_type TEXT,
    geom geometry(Geometry,4326),
    radius_m NUMERIC(10,2),
    description TEXT,
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.10 HEARTBEATS (Device health/ping)
CREATE TABLE IF NOT EXISTS bronze.heartbeats (
    imei TEXT NOT NULL,
    gate_time TIMESTAMPTZ NOT NULL,
    power_level SMALLINT,
    gsm_signal SMALLINT,
    acc_status SMALLINT,
    power_status SMALLINT,
    fortify SMALLINT,
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (imei, gate_time)
);

-- 5.11 INGESTION LOG (Metadata for tracking loads)
CREATE TABLE IF NOT EXISTS bronze.ingestion_log (
    id BIGINT PRIMARY KEY,
    run_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    endpoint TEXT NOT NULL,
    imei_count INTEGER DEFAULT 0 NOT NULL,
    rows_upserted INTEGER DEFAULT 0 NOT NULL,
    rows_inserted INTEGER DEFAULT 0 NOT NULL,
    duration_ms INTEGER DEFAULT 0 NOT NULL,
    success BOOLEAN DEFAULT true NOT NULL,
    error_code TEXT,
    error_message TEXT,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.12 LBS READINGS (Fallback positioning)
CREATE TABLE IF NOT EXISTS bronze.lbs_readings (
    id BIGINT PRIMARY KEY,
    imei TEXT NOT NULL,
    gate_time TIMESTAMPTZ NOT NULL,
    post_type TEXT,
    lbs_data JSONB,
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.13 LIVE POSITIONS (Current state snapshot)
CREATE TABLE IF NOT EXISTS bronze.live_positions (
    imei TEXT PRIMARY KEY,
    geom geometry(Point,4326),
    lat DOUBLE PRECISION,
    lng DOUBLE PRECISION,
    pos_type TEXT,
    confidence SMALLINT,
    gps_time TIMESTAMPTZ,
    hb_time TIMESTAMPTZ,
    speed NUMERIC(7,2),
    direction NUMERIC(6,2),
    acc_status TEXT,
    gps_signal SMALLINT,
    gps_num SMALLINT,
    elec_quantity NUMERIC(5,2),
    power_value NUMERIC(5,2),
    battery_power_val NUMERIC(5,2),
    tracker_oil TEXT,
    temperature NUMERIC(8,2),
    current_mileage NUMERIC(12,2),
    device_status TEXT,
    expire_flag TEXT,
    activation_flag TEXT,
    loc_desc TEXT,
    recorded_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.14 OBD READINGS (Vehicle diagnostics)
CREATE TABLE IF NOT EXISTS bronze.obd_readings (
    id BIGINT PRIMARY KEY,
    imei TEXT,
    reading_time TIMESTAMPTZ,
    engine_rpm INTEGER,
    fuel_level_pct NUMERIC(5,2),
    updated_at TIMESTAMPTZ DEFAULT now(),
    car_type SMALLINT,
    acc_state SMALLINT,
    status_flags INTEGER,
    lat DOUBLE PRECISION,
    lng DOUBLE PRECISION,
    geom geometry(Point,4326),
    obd_data JSONB,
    coolant_temp_c NUMERIC(6,2),
    battery_voltage NUMERIC(5,2),
    intake_pressure NUMERIC(6,2),
    throttle_pct NUMERIC(5,2),
    vehicle_speed NUMERIC(7,2),
    engine_load_pct NUMERIC(5,2),
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.15 PARKING EVENTS
CREATE TABLE IF NOT EXISTS bronze.parking_events (
    id BIGINT PRIMARY KEY,
    imei TEXT NOT NULL,
    event_type TEXT,
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ,
    duration_seconds INTEGER,
    geom geometry(Point,4326),
    address TEXT,
    updated_at TIMESTAMPTZ DEFAULT now(),
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5.16 TEMPERATURE READINGS (Cold chain sensors)
CREATE TABLE IF NOT EXISTS bronze.temperature_readings (
    imei TEXT NOT NULL,
    reading_time TIMESTAMPTZ NOT NULL,
    temperature NUMERIC(6,2),
    humidity_pct NUMERIC(5,2),
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (imei, reading_time)
);

-- Reset role back to superuser
RESET ROLE;
RESET search_path;

-- 6. VERIFICATION GRANTS (Ensure Grafana can query immediately)
GRANT SELECT ON ALL TABLES IN SCHEMA bronze TO grafana_ro;
GRANT USAGE ON SCHEMA bronze TO grafana_ro;