-- Migration 006: Observability (TimescaleDB)
-- Hypertable PKs include time column (SG1); idempotent DDL (SG3); no array_agg in CAGGs (SG2).

-- Enable TimescaleDB extension
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- ─── Time-series metrics ──────────────────────────────────────────────
CREATE TABLE metric_samples (
    ts          TIMESTAMPTZ NOT NULL,
    entity_id   UUID NOT NULL,
    metric      TEXT NOT NULL,
    value       DOUBLE PRECISION NOT NULL,
    tags        JSONB NOT NULL DEFAULT '{}'
);
SELECT create_hypertable('metric_samples', 'ts',
    chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);
CREATE INDEX idx_metrics_entity_ts ON metric_samples(entity_id, ts DESC);
CREATE INDEX idx_metrics_metric_ts ON metric_samples(metric, ts DESC);
DO $$ BEGIN
    PERFORM add_retention_policy('metric_samples', INTERVAL '90 days');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- 1-hour rollups
CREATE MATERIALIZED VIEW metric_rollups_1h WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 hour', ts) AS bucket, entity_id, metric,
           avg(value) AS avg_value, min(value) AS min_value,
           max(value) AS max_value, count(*) AS sample_count
    FROM metric_samples GROUP BY bucket, entity_id, metric;
DO $$ BEGIN
    PERFORM add_continuous_aggregate_policy('metric_rollups_1h',
        start_offset => INTERVAL '2 hours',
        end_offset   => INTERVAL '5 minutes',
        schedule_interval => INTERVAL '1 hour');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- 1-day rollups
CREATE MATERIALIZED VIEW metric_rollups_1d WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', ts) AS bucket, entity_id, metric,
           avg(value) AS avg_value, min(value) AS min_value,
           max(value) AS max_value, count(*) AS sample_count
    FROM metric_samples GROUP BY bucket, entity_id, metric;
DO $$ BEGIN
    PERFORM add_continuous_aggregate_policy('metric_rollups_1d',
        start_offset => INTERVAL '2 days',
        end_offset   => INTERVAL '1 hour',
        schedule_interval => INTERVAL '1 day');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- ─── Audit log ────────────────────────────────────────────────────────
CREATE TABLE audit_log (
    id          BIGINT GENERATED ALWAYS AS IDENTITY,
    ts          TIMESTAMPTZ NOT NULL DEFAULT now(),
    actor_type  TEXT NOT NULL,
    actor_id    UUID,
    action      TEXT NOT NULL,
    entity_id   UUID,
    method      TEXT,
    path        TEXT,
    status_code INTEGER,
    detail      JSONB NOT NULL DEFAULT '{}',
    source_ip   TEXT,
    correlation_id TEXT,
    PRIMARY KEY (id, ts)
);
SELECT create_hypertable('audit_log', 'ts',
    chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);
CREATE INDEX idx_audit_actor ON audit_log(actor_type, actor_id, ts DESC);
CREATE INDEX idx_audit_entity ON audit_log(entity_id, ts DESC);
CREATE INDEX idx_audit_action ON audit_log(action, ts DESC);
CREATE INDEX idx_audit_correlation ON audit_log(correlation_id);
DO $$ BEGIN
    PERFORM add_retention_policy('audit_log', INTERVAL '365 days');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- ─── Event log ────────────────────────────────────────────────────────
CREATE TABLE events (
    id          BIGINT GENERATED ALWAYS AS IDENTITY,
    ts          TIMESTAMPTZ NOT NULL DEFAULT now(),
    type        TEXT NOT NULL,
    entity_id   UUID,
    severity    TEXT NOT NULL DEFAULT 'info',
    source      TEXT NOT NULL,
    data        JSONB NOT NULL DEFAULT '{}',
    correlation_id TEXT,
    PRIMARY KEY (id, ts)
);
SELECT create_hypertable('events', 'ts',
    chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);
CREATE INDEX idx_events_type_ts ON events(type, ts DESC);
CREATE INDEX idx_events_entity_ts ON events(entity_id, ts DESC);
CREATE INDEX idx_events_severity_ts ON events(severity, ts DESC);
CREATE INDEX idx_events_correlation ON events(correlation_id);
DO $$ BEGIN
    PERFORM add_retention_policy('events', INTERVAL '90 days');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- ─── Agent activity ──────────────────────────────────────────────────
CREATE TABLE agent_activity (
    id          BIGINT GENERATED ALWAYS AS IDENTITY,
    ts          TIMESTAMPTZ NOT NULL DEFAULT now(),
    agent_id    UUID NOT NULL,
    session_id  TEXT,
    activity_type TEXT NOT NULL,
    tool_name   TEXT,
    entity_id   UUID,
    input_summary TEXT,
    output_summary TEXT,
    duration_ms INTEGER,
    token_count INTEGER,
    success     BOOLEAN,
    correlation_id TEXT,
    PRIMARY KEY (id, ts)
);
SELECT create_hypertable('agent_activity', 'ts',
    chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);
CREATE INDEX idx_agent_activity_agent_ts ON agent_activity(agent_id, ts DESC);
CREATE INDEX idx_agent_activity_type_ts ON agent_activity(activity_type, ts DESC);
CREATE INDEX idx_agent_activity_entity ON agent_activity(entity_id, ts DESC);
CREATE INDEX idx_agent_activity_correlation ON agent_activity(correlation_id);
DO $$ BEGIN
    PERFORM add_retention_policy('agent_activity', INTERVAL '90 days');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;

-- ─── Ledger view (R3-11: not a fourth write path) ────────────────────
CREATE VIEW ledger AS
SELECT e.created_at AS ts, e.entity_id AS execution_id, e.target_entity_id,
       e.action, e.risk_class, e.status, e.verified,
       c.route, c.reasoning, a.status AS approval_status, a.decided_by,
       e.agent_id, e.correlation_id
FROM executions e
LEFT JOIN classifications c ON c.entity_id = e.classification_id
LEFT JOIN approvals a       ON a.entity_id = e.approval_id;
