-- =============================================================================
-- ALPHA_PROJECT — Database "pompeo" — Schema v2
-- =============================================================================
-- Applicare su: postgresql://martin@postgres.persistence.svc.cluster.local:5432/pompeo
--
-- Esecuzione dal cluster:
--   sudo microk8s kubectl run psql-pompeo --rm -it \
--     --image=postgres:17-alpine --namespace=persistence \
--     --env="PGPASSWORD=<password>" --restart=Never \
--     -- psql "postgresql://martin@postgres:5432/pompeo" -f /dev/stdin < postgres.sql
--
-- Esecuzione via port-forward:
--   sudo microk8s kubectl port-forward svc/postgres -n persistence 5432:5432
--   psql "postgresql://martin@localhost:5432/pompeo" -f postgres.sql
-- =============================================================================

\c pompeo

-- ---------------------------------------------------------------------------
-- Estensioni
-- ---------------------------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";  -- full-text similarity search su subject/detail


-- =============================================================================
-- 1. USER_PROFILE
--    Preferenze statiche per utente. Aggiornata manualmente o via agent action.
--    user_id 'shared' = preferenze della casa (visibili a tutti).
-- =============================================================================
CREATE TABLE IF NOT EXISTS user_profile (
  user_id            TEXT PRIMARY KEY,
  display_name       TEXT,
  language           TEXT        NOT NULL DEFAULT 'it',
  timezone           TEXT        NOT NULL DEFAULT 'Europe/Rome',
  notification_style TEXT        NOT NULL DEFAULT 'concise',  -- 'concise' | 'verbose'
  quiet_start        TIME        NOT NULL DEFAULT '23:00',
  quiet_end          TIME        NOT NULL DEFAULT '07:00',
  preferences        JSONB,      -- freeform: soglie, preferenze extra per agente
  updated_at         TIMESTAMP   NOT NULL DEFAULT now()
);

-- Utenti iniziali
INSERT INTO user_profile (user_id, display_name) VALUES
  ('martin', 'Martin'),
  ('shared', 'Shared')
ON CONFLICT (user_id) DO NOTHING;


-- =============================================================================
-- 2. MEMORY_FACTS
--    Fatti episodici prodotti da tutti gli agenti. TTL tramite expires_at.
--    qdrant_id: riferimento al punto vettoriale corrispondente nella collection "episodes".
-- =============================================================================
CREATE TABLE IF NOT EXISTS memory_facts (
  id               UUID      PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id          TEXT      NOT NULL DEFAULT 'martin',
  source           TEXT      NOT NULL,   -- 'email' | 'calendar' | 'iot' | 'paperless' | 'n8n' | ...
  source_ref       TEXT,                 -- ID esterno per dedup (es. UID evento Google, thread_id email)
  category         TEXT,                 -- 'finance' | 'personal' | 'work' | 'health' | ...
  subject          TEXT,
  detail           JSONB,                -- payload flessibile per-source
  action_required  BOOLEAN   NOT NULL DEFAULT false,
  action_text      TEXT,
  created_at       TIMESTAMP NOT NULL DEFAULT now(),
  expires_at       TIMESTAMP,            -- NULL = permanente
  qdrant_id        UUID,                 -- FK logico → collection "episodes"
  pompeo_note      TEXT,                 -- inner monologue dell'LLM al momento dell'insert
  entity_refs      JSONB                 -- entità estratte: {people, places, products, amounts}
);

CREATE INDEX IF NOT EXISTS idx_memory_facts_user_source_cat
  ON memory_facts(user_id, source, category);

CREATE INDEX IF NOT EXISTS idx_memory_facts_expires
  ON memory_facts(expires_at)
  WHERE expires_at IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_memory_facts_action
  ON memory_facts(user_id, action_required)
  WHERE action_required = true;

CREATE INDEX IF NOT EXISTS idx_memory_facts_source_ref
  ON memory_facts(source_ref)
  WHERE source_ref IS NOT NULL;

-- Dedup: prevents duplicate inserts for same event (used by Calendar Agent and others)
CREATE UNIQUE INDEX IF NOT EXISTS memory_facts_dedup_idx
  ON memory_facts(user_id, source, source_ref)
  WHERE source_ref IS NOT NULL;


-- =============================================================================
-- 3. FINANCE_DOCUMENTS
--    Documenti finanziari strutturati (bollette, fatture, cedolini).
--    paperless_doc_id: riferimento al documento in Paperless-ngx.
-- =============================================================================
CREATE TABLE IF NOT EXISTS finance_documents (
  id                UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id           TEXT        NOT NULL DEFAULT 'martin',
  paperless_doc_id  INT,                  -- ID documento in Paperless-ngx
  correspondent     TEXT,
  amount            NUMERIC(10,2),
  currency          TEXT        NOT NULL DEFAULT 'EUR',
  doc_date          DATE,
  doc_type          TEXT,                 -- 'bolletta' | 'fattura' | 'cedolino' | ...
  tags              TEXT[],
  raw_text          TEXT,                 -- testo OCR grezzo (per embedding)
  created_at        TIMESTAMP   NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_finance_docs_user_date
  ON finance_documents(user_id, doc_date DESC);

CREATE INDEX IF NOT EXISTS idx_finance_docs_correspondent
  ON finance_documents(user_id, correspondent);


-- =============================================================================
-- 4. BEHAVIORAL_CONTEXT
--    Contesto comportamentale prodotto dall'IoT Agent e dal Calendar Agent.
--    Usato dal Proactive Arbiter per rispettare DND e stimare presence.
-- =============================================================================
CREATE TABLE IF NOT EXISTS behavioral_context (
  id                        UUID      PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id                   TEXT      NOT NULL DEFAULT 'martin',
  event_type                TEXT,     -- 'sport_event' | 'dog_walk' | 'work_session' | 'commute' | ...
  start_at                  TIMESTAMP,
  end_at                    TIMESTAMP,
  do_not_disturb            BOOLEAN   NOT NULL DEFAULT false,
  home_presence_expected    BOOLEAN,
  notes                     TEXT,
  created_at                TIMESTAMP NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_behavioral_ctx_user_time
  ON behavioral_context(user_id, start_at, end_at);

CREATE INDEX IF NOT EXISTS idx_behavioral_ctx_dnd
  ON behavioral_context(user_id, do_not_disturb)
  WHERE do_not_disturb = true;


-- =============================================================================
-- 5. AGENT_MESSAGES
--    Blackboard: ogni agente pubblica qui le proprie osservazioni.
--    Il Proactive Arbiter legge, decide (notify/defer/discard) e aggiorna.
--    Corrisponde al message schema definito in alpha/README.md.
-- =============================================================================
CREATE TABLE IF NOT EXISTS agent_messages (
  id                UUID      PRIMARY KEY DEFAULT gen_random_uuid(),
  agent             TEXT      NOT NULL,  -- 'mail' | 'calendar' | 'iot' | 'finance' | 'infra' | ...
  priority          TEXT      NOT NULL,  -- 'low' | 'high'
  event_type        TEXT      NOT NULL,  -- 'new_fact' | 'reminder' | 'alert' | 'behavioral_observation'
  user_id           TEXT      NOT NULL DEFAULT 'martin',
  subject           TEXT,
  detail            JSONB,
  source_ref        TEXT,                -- ID record Postgres o ref esterna
  expires_at        TIMESTAMP,
  arbiter_decision  TEXT,               -- NULL (pending) | 'notify' | 'defer' | 'discard'
  arbiter_reason    TEXT,
  created_at        TIMESTAMP NOT NULL DEFAULT now(),
  processed_at      TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_agent_msgs_pending
  ON agent_messages(user_id, priority, created_at)
  WHERE arbiter_decision IS NULL;

CREATE INDEX IF NOT EXISTS idx_agent_msgs_agent_type
  ON agent_messages(agent, event_type, created_at);

CREATE INDEX IF NOT EXISTS idx_agent_msgs_expires
  ON agent_messages(expires_at)
  WHERE expires_at IS NOT NULL AND arbiter_decision IS NULL;


-- =============================================================================
-- 6. HA_SENSOR_CONFIG
--    Allowlist dinamica dei sensori Home Assistant monitorati dall'IoT Agent.
--    Pattern = regex, matchato contro gli entity_id di Home Assistant.
--    Evita regole hardcoded nel workflow — aggiungere sensori = INSERT.
-- =============================================================================
CREATE TABLE IF NOT EXISTS ha_sensor_config (
  id          SERIAL    PRIMARY KEY,
  pattern     TEXT      NOT NULL,          -- regex pattern (es. 'sensor\.pixel_10_.*')
  user_id     TEXT      NOT NULL DEFAULT 'martin',
  group_name  TEXT      NOT NULL,          -- 'mobile_device' | 'work_presence' | 'entertainment' | ...
  description TEXT,
  active      BOOLEAN   NOT NULL DEFAULT true,
  created_at  TIMESTAMP NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_ha_sensor_config_user
  ON ha_sensor_config(user_id, active);

-- Seed: sensori significativi per Martin
INSERT INTO ha_sensor_config (pattern, user_id, group_name, description) VALUES
  ('sensor\.pixel_10_.*',                  'martin', 'mobile_device',  'Tutti i sensori Pixel 10'),
  ('binary_sensor\.pixel_10_.*',           'martin', 'mobile_device',  'Sensori binari Pixel 10'),
  ('device_tracker\.ey_hp',               'martin', 'work_presence',  'Laptop EY (router tracker)'),
  ('media_player\.spotify_martin',         'martin', 'entertainment',  'Spotify Martin'),
  ('person\.martin_tahiraj',              'martin', 'presence',       'Zona GPS Martin'),
  ('sensor\.pixel_watch_.*',              'martin', 'wearable',       'Pixel Watch 4 (futuro)'),
  ('sensor\.pixel_10_heart_rate',         'martin', 'health',         'Frequenza cardiaca'),
  ('sensor\.pixel_10_daily_steps',        'martin', 'health',         'Passi giornalieri'),
  ('sensor\.pixel_10_sleep_duration',     'martin', 'health',         'Durata sonno'),
  ('sensor\.pixel_10_next_alarm',         'martin', 'routine',        'Prossima sveglia')
ON CONFLICT DO NOTHING;


-- =============================================================================
-- 7. CONTACTS
--    Grafo di persone multi-tenant. Ogni riga = una relazione (user_id → subject).
--    Traversabile dall'LLM: user_id=martin → cugino → euris, poi user_id=euris → padre → mujsi
--    → l'LLM inferisce che Mujsi è lo zio di Martin.
--    Il campo 'details' è narrativa libera ottimizzata per consumo LLM.
-- =============================================================================
CREATE TABLE IF NOT EXISTS contacts (
  id           UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id      TEXT        NOT NULL,                     -- chi "possiede" questa relazione
  subject      TEXT        NOT NULL,                     -- nome normalizzato del contatto
  relation     TEXT        NOT NULL,                     -- 'cugino' | 'zio' | 'madre' | 'moglie' | 'amico' | ...
  city         TEXT,
  country      TEXT,
  profession   TEXT,
  aliases      TEXT[],                                   -- soprannomi/varianti nome ['Eri', 'Mucho']
  born_year    INT,
  details      TEXT,                                     -- narrativa libera per l'LLM (storia, carattere, note)
  metadata     JSONB,                                    -- extra strutturato: {email, phone, children, spouse, ...}
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (user_id, subject)
);

CREATE INDEX IF NOT EXISTS idx_contacts_user
  ON contacts(user_id);

CREATE INDEX IF NOT EXISTS idx_contacts_subject_trgm
  ON contacts USING gin(subject gin_trgm_ops);           -- similarity search su nome

CREATE INDEX IF NOT EXISTS idx_contacts_aliases
  ON contacts USING gin(aliases);                        -- ricerca per alias/soprannome


-- =============================================================================
-- 8. MEMORY_FACTS_ARCHIVE
--    Fatti scaduti spostati qui dal cleanup settimanale.
--    Struttura identica a memory_facts + campi archivio.
--    Un punto Qdrant "episodio settimanale" riassume il batch archiviato.
-- =============================================================================
CREATE TABLE IF NOT EXISTS memory_facts_archive (
  id               UUID        NOT NULL,
  user_id          TEXT        NOT NULL DEFAULT 'martin',
  source           TEXT        NOT NULL,
  source_ref       TEXT,
  category         TEXT,
  subject          TEXT,
  detail           JSONB,
  action_required  BOOLEAN     NOT NULL DEFAULT false,
  action_text      TEXT,
  pompeo_note      TEXT,
  entity_refs      JSONB,
  qdrant_id        UUID,
  created_at       TIMESTAMPTZ NOT NULL,
  expires_at       TIMESTAMPTZ,
  archived_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  archive_reason   TEXT        NOT NULL DEFAULT 'expired'  -- 'expired' | 'superseded' | 'merged'
);

CREATE INDEX IF NOT EXISTS idx_mf_archive_user_date
  ON memory_facts_archive(user_id, archived_at DESC);

CREATE INDEX IF NOT EXISTS idx_mf_archive_source
  ON memory_facts_archive(user_id, source, category);


-- =============================================================================
-- Fine script
-- =============================================================================
\echo '✅ Schema pompeo applicato correttamente.'
\echo '   Tabelle: user_profile, memory_facts, memory_facts_archive, contacts, finance_documents, behavioral_context, agent_messages, ha_sensor_config'
