-- OfficeCom Sentinel central reporting store.
-- Apply once as a PostgreSQL administrator to the dedicated ocsentinel database.

BEGIN;

CREATE SCHEMA IF NOT EXISTS ocsentinel;

CREATE TABLE IF NOT EXISTS ocsentinel.device (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    machine_name TEXT NOT NULL,
    machine_name_key TEXT NOT NULL UNIQUE,
    first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    last_client_version TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS ocsentinel.scan_report (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    device_id BIGINT NOT NULL REFERENCES ocsentinel.device(id) ON DELETE CASCADE,
    generated_at_utc TIMESTAMPTZ NOT NULL,
    received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    client_version TEXT NOT NULL,
    alert_state TEXT NOT NULL CHECK (alert_state IN ('ok', 'warning', 'critical', 'unknown')),
    base_alert_state TEXT NOT NULL CHECK (base_alert_state IN ('ok', 'warning', 'critical', 'unknown')),
    total_events INTEGER NOT NULL CHECK (total_events >= 0),
    unique_ip_count INTEGER NOT NULL CHECK (unique_ip_count >= 0),
    cve_total INTEGER NOT NULL DEFAULT 0 CHECK (cve_total >= 0),
    cve_critical INTEGER NOT NULL DEFAULT 0 CHECK (cve_critical >= 0),
    payload_sha256 CHAR(64) NOT NULL,
    payload JSONB NOT NULL,
    UNIQUE (device_id, generated_at_utc, payload_sha256)
);

CREATE INDEX IF NOT EXISTS ix_ocsentinel_scan_report_device_received
    ON ocsentinel.scan_report (device_id, received_at DESC);

CREATE INDEX IF NOT EXISTS ix_ocsentinel_scan_report_alert_received
    ON ocsentinel.scan_report (alert_state, received_at DESC);

CREATE TABLE IF NOT EXISTS ocsentinel.ingest_nonce (
    nonce CHAR(32) PRIMARY KEY,
    device_id BIGINT NOT NULL REFERENCES ocsentinel.device(id) ON DELETE CASCADE,
    received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    expires_at TIMESTAMPTZ NOT NULL
);

CREATE INDEX IF NOT EXISTS ix_ocsentinel_ingest_nonce_expires
    ON ocsentinel.ingest_nonce (expires_at);

CREATE TABLE IF NOT EXISTS ocsentinel.weekly_organization_report (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    organization_id TEXT NOT NULL,
    organization_name TEXT NOT NULL,
    period_start_utc TIMESTAMPTZ NOT NULL,
    period_end_utc TIMESTAMPTZ NOT NULL,
    generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    device_count INTEGER NOT NULL DEFAULT 0,
    warning_count INTEGER NOT NULL DEFAULT 0,
    critical_count INTEGER NOT NULL DEFAULT 0,
    total_events INTEGER NOT NULL DEFAULT 0,
    unique_ips INTEGER NOT NULL DEFAULT 0,
    cve_total INTEGER NOT NULL DEFAULT 0,
    cve_critical INTEGER NOT NULL DEFAULT 0,
    report_html TEXT NOT NULL,
    summary JSONB NOT NULL DEFAULT '{}'::jsonb,
    UNIQUE (organization_id, period_start_utc)
);

CREATE INDEX IF NOT EXISTS ix_ocsentinel_weekly_report_organization_generated
    ON ocsentinel.weekly_organization_report (organization_id, generated_at DESC);

CREATE OR REPLACE VIEW ocsentinel.current_device_status AS
SELECT DISTINCT ON (d.id)
    d.machine_name,
    d.first_seen_at,
    d.last_seen_at,
    d.last_client_version,
    r.generated_at_utc,
    r.received_at,
    r.alert_state,
    r.base_alert_state,
    r.total_events,
    r.unique_ip_count,
    r.cve_total,
    r.cve_critical,
    r.payload
FROM ocsentinel.device AS d
LEFT JOIN ocsentinel.scan_report AS r ON r.device_id = d.id
ORDER BY d.id, r.generated_at_utc DESC NULLS LAST, r.received_at DESC NULLS LAST;

CREATE OR REPLACE VIEW ocsentinel.organization_summary AS
SELECT
    count(*) FILTER (WHERE generated_at_utc IS NOT NULL) AS devices_reporting,
    count(*) FILTER (WHERE alert_state = 'warning') AS devices_warning,
    count(*) FILTER (WHERE alert_state = 'critical') AS devices_critical,
    coalesce(sum(total_events), 0) AS total_events,
    coalesce(sum(unique_ip_count), 0) AS total_unique_ips,
    coalesce(sum(cve_total), 0) AS total_cves,
    coalesce(sum(cve_critical), 0) AS critical_cves,
    max(received_at) AS last_report_received_at
FROM ocsentinel.current_device_status;

COMMIT;
