-- ============================================================
-- Rulesets schema (DB: rules)
-- ------------------------------------------------------------
-- Un ruleset e' una collezione versionata di "items" (paths
-- Signal K per i logs, codici openmeteo per i forecasts, ...).
-- Modello:
--   * 5 tipi fissi: logs | forecast_current | forecast_hourly
--                   | marine_current | marine_hourly
--   * Un solo ruleset puo' essere "active" per ciascun tipo.
--   * Le versioni sono triple di interi 1..100 (major.build.patch).
--   * Gli items sono JSONB per massima flessibilita'.
--   * Ogni item ha un "ref" stabile scelto dall'utente: e' la
--     chiave logica che garantisce continuita' su InfluxDB anche
--     se il path del sensore cambia.
--   * Le deployments tracciano quale ruleset-version e' stato
--     pushato ad ogni sensore.
-- ============================================================

CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ─────────────────────────────────────────────────────────────
-- RULESETS
-- ─────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS rulesets (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    type            TEXT NOT NULL
                    CHECK (type IN ('logs','forecast_current','forecast_hourly','marine_current','marine_hourly')),
    version_major   SMALLINT NOT NULL DEFAULT 1 CHECK (version_major BETWEEN 1 AND 100),
    version_build   SMALLINT NOT NULL DEFAULT 0 CHECK (version_build BETWEEN 0 AND 100),
    version_patch   SMALLINT NOT NULL DEFAULT 0 CHECK (version_patch BETWEEN 0 AND 100),
    description     TEXT NOT NULL DEFAULT '',
    tags            TEXT[] NOT NULL DEFAULT '{}',
    -- items: [{ ref, path, enabled, meta: {...} }, ...]
    -- ref: identificatore logico stabile (chiave su Influx)
    -- path: SK path (logs) | codice openmeteo (forecast/marine)
    -- meta: { name, unit, measurement, sk_path, group_name, category, ... }
    items           JSONB NOT NULL DEFAULT '[]'::jsonb,
    active          BOOLEAN NOT NULL DEFAULT false,
    archived        BOOLEAN NOT NULL DEFAULT false,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    UNIQUE (type, version_major, version_build, version_patch)
);

-- solo UN ruleset active per tipo (archiviati esclusi)
CREATE UNIQUE INDEX IF NOT EXISTS rulesets_one_active_per_type
    ON rulesets (type)
    WHERE active = true AND archived = false;

CREATE INDEX IF NOT EXISTS rulesets_type_idx       ON rulesets (type);
CREATE INDEX IF NOT EXISTS rulesets_active_idx     ON rulesets (type) WHERE active = true;
CREATE INDEX IF NOT EXISTS rulesets_archived_idx   ON rulesets (archived);
CREATE INDEX IF NOT EXISTS rulesets_items_gin_idx  ON rulesets USING GIN (items);

-- Validazione items: array di oggetti con almeno ref+path
CREATE OR REPLACE FUNCTION rulesets_validate_items() RETURNS trigger AS $$
DECLARE
    refs TEXT[];
BEGIN
    IF jsonb_typeof(NEW.items) <> 'array' THEN
        RAISE EXCEPTION 'items must be a JSON array';
    END IF;

    -- tutti gli item devono avere ref non vuoto e path (anche vuoto ammesso)
    IF EXISTS (
        SELECT 1 FROM jsonb_array_elements(NEW.items) it
        WHERE jsonb_typeof(it) <> 'object'
           OR NULLIF(it->>'ref','') IS NULL
    ) THEN
        RAISE EXCEPTION 'every item must be an object with a non-empty "ref"';
    END IF;

    -- unicita' ref all'interno dello stesso ruleset
    SELECT array_agg(it->>'ref') INTO refs
    FROM jsonb_array_elements(NEW.items) it;
    IF (SELECT count(DISTINCT x) FROM unnest(refs) x) <> COALESCE(array_length(refs,1),0) THEN
        RAISE EXCEPTION 'item refs must be unique within the ruleset';
    END IF;

    NEW.updated_at := NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS rulesets_validate_trigger ON rulesets;
CREATE TRIGGER rulesets_validate_trigger
    BEFORE INSERT OR UPDATE ON rulesets
    FOR EACH ROW EXECUTE FUNCTION rulesets_validate_items();


-- ─────────────────────────────────────────────────────────────
-- DEPLOYMENTS
--   Traccia quale ruleset-version e' stato pushato ad ogni
--   sensore (per tipo). Un solo ruleset per (sensor,type).
-- ─────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS ruleset_deployments (
    sensor_name     TEXT NOT NULL,
    type            TEXT NOT NULL,
    ruleset_id      UUID NOT NULL REFERENCES rulesets(id) ON DELETE CASCADE,
    deployed_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    acked_at        TIMESTAMPTZ,
    PRIMARY KEY (sensor_name, type)
);

CREATE INDEX IF NOT EXISTS ruleset_deployments_ruleset_idx
    ON ruleset_deployments (ruleset_id);


-- ─────────────────────────────────────────────────────────────
-- AUDIT LOG (opzionale ma utile)
-- ─────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS ruleset_changes (
    id              BIGSERIAL PRIMARY KEY,
    ruleset_id      UUID,
    type            TEXT,
    action          TEXT NOT NULL,          -- created | updated | activated | archived | deleted | deployed
    user_id         TEXT,
    payload         JSONB,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS ruleset_changes_ruleset_idx ON ruleset_changes (ruleset_id);
CREATE INDEX IF NOT EXISTS ruleset_changes_created_idx ON ruleset_changes (created_at DESC);
