-- Versioned v8 upgrade for immutable Workbench replay and the AgentRun Kafka projector.

ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS outbox_event_id TEXT;
ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS entity_family TEXT;
ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS entity_id TEXT;

-- Pre-v8 rows did not store immutable fact payloads. Mark them as cutover rows so
-- recovery advances its cursor without fabricating message/turn/trace entities.
UPDATE workbench_projection_outbox
SET outbox_event_id = COALESCE(outbox_event_id, 'legacy:' || outbox_seq::text),
    entity_family = COALESCE(entity_family, 'legacy'),
    entity_id = COALESCE(entity_id, 'legacy:' || outbox_seq::text)
WHERE outbox_event_id IS NULL OR entity_family IS NULL OR entity_id IS NULL;

ALTER TABLE workbench_projection_outbox ALTER COLUMN outbox_event_id SET NOT NULL;
ALTER TABLE workbench_projection_outbox ALTER COLUMN entity_family SET NOT NULL;
ALTER TABLE workbench_projection_outbox ALTER COLUMN entity_id SET NOT NULL;
DROP INDEX IF EXISTS idx_workbench_projection_outbox_event_id;
CREATE UNIQUE INDEX idx_workbench_projection_outbox_event_id ON workbench_projection_outbox(outbox_event_id);

CREATE TABLE IF NOT EXISTS workbench_kafka_inbox (
  source_topic TEXT NOT NULL,
  source_partition INTEGER NOT NULL,
  source_offset BIGINT NOT NULL,
  source_key TEXT,
  source_event_id TEXT,
  input_sha256 TEXT NOT NULL,
  trace_id TEXT,
  session_id TEXT,
  run_id TEXT,
  command_id TEXT,
  source_seq INTEGER NOT NULL DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'processing',
  projected_seq INTEGER,
  envelope_json TEXT NOT NULL DEFAULT '{}',
  error_json TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  projected_at TEXT,
  PRIMARY KEY (source_topic, source_partition, source_offset)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_source_event ON workbench_kafka_inbox(source_topic, source_event_id) WHERE source_event_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_status_updated ON workbench_kafka_inbox(status, updated_at, source_topic, source_partition, source_offset);
CREATE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_trace ON workbench_kafka_inbox(trace_id, source_seq);

CREATE TABLE IF NOT EXISTS workbench_kafka_dlq (
  dlq_seq BIGSERIAL PRIMARY KEY,
  source_topic TEXT NOT NULL,
  source_partition INTEGER NOT NULL,
  source_offset BIGINT NOT NULL,
  source_event_id TEXT,
  input_sha256 TEXT NOT NULL,
  error_code TEXT NOT NULL,
  error_message TEXT NOT NULL,
  envelope_json TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_workbench_kafka_dlq_transport ON workbench_kafka_dlq(source_topic, source_partition, source_offset);

CREATE TABLE IF NOT EXISTS hwlab_kafka_outbox (
  outbox_seq BIGSERIAL PRIMARY KEY,
  event_id TEXT NOT NULL UNIQUE,
  topic TEXT NOT NULL,
  partition_key TEXT NOT NULL,
  payload_json TEXT NOT NULL,
  headers_json TEXT NOT NULL DEFAULT '{}',
  attempt_count INTEGER NOT NULL DEFAULT 0,
  next_attempt_at TEXT NOT NULL,
  lease_owner TEXT,
  lease_expires_at TEXT,
  published_at TEXT,
  last_error TEXT,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_hwlab_kafka_outbox_due ON hwlab_kafka_outbox(published_at, next_attempt_at, lease_expires_at, outbox_seq);

CREATE OR REPLACE FUNCTION notify_hwlab_workbench_projection() RETURNS trigger AS $$
BEGIN
  PERFORM pg_notify(
    'hwlab_workbench_projection',
    json_build_object(
      'sessionId', NEW.session_id,
      'traceId', NEW.trace_id,
      'outboxSeq', NEW.outbox_seq
    )::text
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_notify_hwlab_workbench_projection ON workbench_projection_outbox;
CREATE TRIGGER trg_notify_hwlab_workbench_projection
AFTER INSERT ON workbench_projection_outbox
FOR EACH ROW EXECUTE FUNCTION notify_hwlab_workbench_projection();

INSERT INTO hwlab_schema_migrations (id, schema_version, applied_at, migration_json)
VALUES (
  '0008_workbench_kafka_realtime',
  'runtime-durable-postgres-v8',
  CURRENT_TIMESTAMP,
  '{"path":"internal/db/migrations/0008_workbench_kafka_realtime.sql","runtime":"cloud-api"}'
)
ON CONFLICT (id) DO UPDATE SET
  schema_version = EXCLUDED.schema_version,
  migration_json = EXCLUDED.migration_json;
