-- =============================================================================
-- Fireside Communications — Tracksolid Pro Fleet Telemetry
-- PostgreSQL 16 + PostGIS 3 + TimescaleDB 2.15
-- Complete Database Bootstrap Script (Revised & Unified)
-- =============================================================================
-- Author: DevOps / Telematics Specialist
-- Version: 2.0 (DWH + TimescaleDB Integrated)
-- =============================================================================

-- =============================================================================
-- STEP 1 — DATABASE, EXTENSIONS, ROLES
-- =============================================================================

-- Database creation (Run manually if needed, or logical script starts here)
-- CREATE DATABASE tracksolid_db ENCODING = 'UTF8';
-- \connect tracksolid_db

-- ── Extensions ────────────────────────────────────────────────────────────────
CREATE EXTENSION IF NOT EXISTS postgis CASCADE;
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gist;

-- ── Roles ─────────────────────────────────────────────────────────────────────
DO $$
BEGIN
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'tracksolid_owner') THEN
        CREATE ROLE tracksolid_owner WITH LOGIN PASSWORD 'SET_PASSWORD_IN_ENV';
    END IF;
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'grafana_ro') THEN
        CREATE ROLE grafana_ro WITH LOGIN PASSWORD 'SET_PASSWORD_IN_ENV';
    END IF;
END$$;

GRANT CONNECT ON DATABASE tracksolid_db TO tracksolid_owner;
GRANT CONNECT ON DATABASE tracksolid_db TO grafana_ro;

-- =============================================================================
-- STEP 2 — SCHEMAS
-- =============================================================================

CREATE SCHEMA IF NOT EXISTS tracksolid AUTHORIZATION tracksolid_owner;
CREATE SCHEMA IF NOT EXISTS infrastructure AUTHORIZATION tracksolid_owner;
CREATE SCHEMA IF NOT EXISTS dwh_gold AUTHORIZATION tracksolid_owner;

ALTER DATABASE tracksolid_db SET search_path TO tracksolid, infrastructure, dwh_gold, public;

-- =============================================================================
-- STEP 3 — OPERATIONAL TABLES (tracksolid)
-- =============================================================================

-- 3.01 Master Device Registry
CREATE TABLE IF NOT EXISTS tracksolid.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 NOT NULL DEFAULT 1,
    status              TEXT DEFAULT 'active',
    city                TEXT,
    current_mileage_km  NUMERIC(12,2),
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_synced_at      TIMESTAMPTZ
);

-- 3.02 Token Cache
CREATE TABLE IF NOT EXISTS tracksolid.api_token_cache (
    id              BIGSERIAL PRIMARY KEY,
    account         TEXT NOT NULL UNIQUE,
    access_token    TEXT NOT NULL,
    refresh_token   TEXT,
    app_key         TEXT,
    expires_at      TIMESTAMPTZ NOT NULL,
    obtained_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 3.03 Ingestion Health Log
CREATE TABLE IF NOT EXISTS tracksolid.ingestion_log (
    id              BIGSERIAL PRIMARY KEY,
    run_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    endpoint        TEXT NOT NULL,
    imei_count      INTEGER NOT NULL DEFAULT 0,
    rows_upserted   INTEGER NOT NULL DEFAULT 0,
    rows_inserted   INTEGER NOT NULL DEFAULT 0,
    duration_ms     INTEGER NOT NULL DEFAULT 0,
    success         BOOLEAN NOT NULL DEFAULT TRUE,
    error_code      TEXT,
    error_message   TEXT
);

-- 3.04 Live Positions (Hot Snapshots)
CREATE TABLE IF NOT EXISTS tracksolid.live_positions (
    imei                TEXT PRIMARY KEY REFERENCES tracksolid.devices(imei),
    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 NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 3.05 Position History (Hypertable Source)
CREATE TABLE IF NOT EXISTS tracksolid.position_history (
    imei                TEXT NOT NULL REFERENCES tracksolid.devices(imei),
    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(),
    PRIMARY KEY (imei, gps_time)
);

-- 3.06 Trip Summaries
CREATE TABLE IF NOT EXISTS tracksolid.trips (
    id                  BIGSERIAL PRIMARY KEY,
    imei                TEXT NOT NULL REFERENCES tracksolid.devices(imei),
    start_time          TIMESTAMPTZ NOT NULL,
    end_time            TIMESTAMPTZ,
    start_geom          geometry(Point, 4326),
    end_geom            geometry(Point, 4326),
    distance_m          NUMERIC(12,2), -- QA-02: Stored in Meters
    avg_speed_kmh       NUMERIC(7,2),
    max_speed_kmh       NUMERIC(7,2),
    updated_at          TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT trips_imei_start_unique UNIQUE (imei, start_time)
);

-- 3.07 Parking & Idling
CREATE TABLE IF NOT EXISTS tracksolid.parking_events (
    id                  BIGSERIAL PRIMARY KEY,
    imei                TEXT NOT NULL REFERENCES tracksolid.devices(imei),
    event_type          TEXT CHECK (event_type IN ('parking', 'idling')),
    start_time          TIMESTAMPTZ NOT NULL,
    end_time            TIMESTAMPTZ,
    duration_seconds    INTEGER,
    geom                geometry(Point, 4326),
    address             TEXT,
    updated_at          TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT parking_dedup UNIQUE (imei, start_time, event_type)
);

-- 3.08 Alarms, OBD, Fault Codes
CREATE TABLE IF NOT EXISTS tracksolid.alarms (
    id BIGSERIAL PRIMARY KEY,
    imei TEXT REFERENCES tracksolid.devices(imei),
    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(),
    CONSTRAINT alarms_dedup UNIQUE (imei, alarm_type, alarm_time)
);

CREATE TABLE IF NOT EXISTS tracksolid.obd_readings (
    id BIGSERIAL PRIMARY KEY,
    imei TEXT REFERENCES tracksolid.devices(imei),
    reading_time TIMESTAMPTZ,
    engine_rpm INTEGER,
    fuel_level_pct NUMERIC(5,2),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT obd_readings_dedup UNIQUE (imei, reading_time)
);

-- =============================================================================
-- STEP 4 — TIMESCALEDB CONFIGURATION
-- =============================================================================

-- Convert to Hypertable
SELECT create_hypertable('tracksolid.position_history', 'gps_time', chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);

-- Enable Compression
ALTER TABLE tracksolid.position_history SET (timescaledb.compress, timescaledb.compress_segmentby = 'imei');
SELECT add_compression_policy('tracksolid.position_history', INTERVAL '14 days');

-- Retention: 90 Days for Hot History
SELECT add_retention_policy('tracksolid.position_history', INTERVAL '90 days');

-- =============================================================================
-- STEP 5 — DATA WAREHOUSE GOLD LAYER (dwh_gold)
-- =============================================================================

CREATE TABLE dwh_gold.dim_vehicles (
    vehicle_key      SERIAL PRIMARY KEY,
    imei             TEXT UNIQUE,
    vehicle_number   TEXT,
    is_active        BOOLEAN DEFAULT TRUE
);

CREATE TABLE dwh_gold.fact_daily_fleet_metrics (
    day              DATE NOT NULL,
    vehicle_key      INTEGER REFERENCES dwh_gold.dim_vehicles(vehicle_key),
    total_distance_km NUMERIC(12,2),
    max_speed_kmh    NUMERIC(7,2),
    idle_hours       NUMERIC(5,2),
    PRIMARY KEY (day, vehicle_key)
);

-- =============================================================================
-- STEP 6 — TRIGGERS & VIEWS
-- =============================================================================

-- Updated_at Trigger Function
CREATE OR REPLACE FUNCTION tracksolid.set_updated_at() RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;

-- Apply trigger to tables with updated_at column
DO $$
DECLARE t TEXT;
BEGIN
    FOR t IN
        SELECT pt.tablename
        FROM pg_tables pt
        JOIN information_schema.columns c
          ON c.table_schema = pt.schemaname AND c.table_name = pt.tablename
        WHERE pt.schemaname = 'tracksolid' AND c.column_name = 'updated_at'
    LOOP
        EXECUTE format('CREATE TRIGGER trg_upd_%I BEFORE UPDATE ON tracksolid.%I FOR EACH ROW EXECUTE FUNCTION tracksolid.set_updated_at()', t, t);
    END LOOP;
END $$;

-- Enriched Live View
CREATE OR REPLACE VIEW tracksolid.v_fleet_status AS
SELECT 
    d.imei, d.vehicle_number, d.driver_name,
    lp.lat, lp.lng, lp.geom, lp.speed, lp.acc_status, lp.gps_time,
    CASE 
        WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'online'
        WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'recent'
        ELSE 'offline'
    END AS connectivity_status,
    EXTRACT(EPOCH FROM (NOW() - lp.gps_time))::int AS seconds_since_fix
FROM tracksolid.devices d
LEFT JOIN tracksolid.live_positions lp USING (imei)
WHERE d.enabled_flag = 1;

-- Ingestion Health View
CREATE OR REPLACE VIEW tracksolid.v_ingestion_health AS
SELECT DISTINCT ON (endpoint) endpoint, run_at, success, error_message, EXTRACT(EPOCH FROM (NOW() - run_at))::int AS seconds_ago
FROM tracksolid.ingestion_log ORDER BY endpoint, run_at DESC;

-- =============================================================================
-- STEP 7 — CONTINUOUS AGGREGATES (Performance)
-- =============================================================================

CREATE MATERIALIZED VIEW tracksolid.v_mileage_daily_cagg
WITH (timescaledb.continuous) AS
SELECT 
    time_bucket('1 day', gps_time) AS bucket,
    imei,
    (MAX(current_mileage) - MIN(current_mileage)) AS dist_km,
    AVG(speed) AS avg_speed
FROM tracksolid.position_history
GROUP BY bucket, imei;

SELECT add_continuous_aggregate_policy('tracksolid.v_mileage_daily_cagg',
    start_offset => INTERVAL '3 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '1 hour');

-- =============================================================================
-- STEP 8 — PERMISSIONS
-- =============================================================================

GRANT USAGE ON SCHEMA tracksolid, infrastructure, dwh_gold TO tracksolid_owner, grafana_ro;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA tracksolid, infrastructure, dwh_gold TO tracksolid_owner;
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid, infrastructure, dwh_gold TO grafana_ro;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_ro;

ALTER DEFAULT PRIVILEGES IN SCHEMA tracksolid GRANT ALL ON TABLES TO tracksolid_owner;
ALTER DEFAULT PRIVILEGES IN SCHEMA tracksolid GRANT SELECT ON TABLES TO grafana_ro;

-- =============================================================================
-- STEP 9 — VERIFICATION
-- =============================================================================
SELECT PostGIS_Full_Version();
SELECT * FROM timescaledb_information.hypertables;