-- ============================================================================
-- 0096_smartsheet_workbook_hub.sql
--
-- Cycle 7 — Smartsheet workbook hub. Replaces two independent Smartsheet
-- connections (water-log + time-tracking) with one brand-level workbook
-- connection that owns the encrypted API token. Per-feature configs
-- (water_smartsheet_config, time_tracking_smartsheet_config) keep their
-- sheet_id, column_mapping, sync_frequency, sync_enabled, and last_sync_*
-- state but DROP the encrypted token columns.
--
-- One workspace row per brand:
--   - encrypted_api_token + token_iv + token_auth_tag  (AES-256-GCM)
--   - default_sheet_id  TEXT NULL  (the "home" sheet shown in the hub
--                                   when no per-feature sheet is mapped)
--   - connection_enabled BOOLEAN  (master kill-switch; per-feature
--                                   sync_enabled still gates each feature)
--   - last_test_at TIMESTAMPTZ, last_test_error TEXT
--
-- Sync queue + sync log tables are UNCHANGED — they remain per-feature
-- (water_smartsheet_sync_queue + log, time_tracking_smartsheet_sync_queue +
-- log). The hub re-homes only the token.
--
-- Migration behavior (idempotent — safe to re-run):
--   1. Create smartsheet_workspace if missing
--   2. Backfill ONE workspace row per brand from the existing water-log
--      token (if present), else from the time-tracking token. Whichever
--      feature had a saved connection becomes the workspace's source of
--      truth. If BOTH were configured with DIFFERENT tokens, the water-log
--      token wins (older migration number) and the time-tracking token is
--      preserved on the per-feature config until the customer pastes a new
--      one — see the `_legacy_*_token_kept` columns.
--   3. DROP the three encrypted columns from both feature configs.
--      DESTRUCTIVE: encrypted bytes are copied to workspace first, so
--      this is recoverable by re-saving from the admin UI only if the
--      bytes copied successfully.
--
-- Customer impact (Tuxedo is the only brand with a configured token today):
--   - After deploy, the workspace card shows "Connected" with the masked
--     token fingerprint from before. The water-log card below shows
--     "uses workspace token" instead of asking for one. No re-paste.
-- ============================================================================

-- ── 1. New workspace table ────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS smartsheet_workspace (
  brand_id UUID PRIMARY KEY REFERENCES brands(id) ON DELETE CASCADE,

  -- AES-256-GCM encrypted API token (one per brand; one per workspace).
  -- Stored as BYTEA so we can keep raw bytes (not base64).
  encrypted_api_token BYTEA NOT NULL,
  token_iv BYTEA NOT NULL,         -- 12 random bytes per encrypt
  token_auth_tag BYTEA NOT NULL,   -- 16-byte GCM auth tag

  -- Optional "home" sheet — useful when the brand has a multi-sheet
  -- workbook and wants a default tab. NULL is fine.
  default_sheet_id TEXT,

  -- Master switch for the workbook connection itself. Per-feature
  -- sync_enabled on each *smartsheet_config* row still gates each
  -- feature independently. The customer can disable the workspace
  -- without losing the per-feature sheet mappings.
  connection_enabled BOOLEAN NOT NULL DEFAULT true,

  -- Set whenever the admin clicks "Test Connection" on the hub card.
  -- Powers the "Last verified: 2 min ago" badge in the UI.
  last_test_at TIMESTAMPTZ,
  last_test_error TEXT,

  -- User IDs from `neon_auth.user`; stored as text so we don't
  -- require a FK to a specific auth backend.
  created_by TEXT,
  updated_by TEXT,

  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Auto-bump updated_at on row UPDATE.
DROP TRIGGER IF EXISTS smartsheet_workspace_set_updated_at ON smartsheet_workspace;
CREATE TRIGGER smartsheet_workspace_set_updated_at
  BEFORE UPDATE ON smartsheet_workspace
  FOR EACH ROW EXECUTE FUNCTION set_updated_at();

-- RLS: brand-scoped, same pattern as existing smartsheet tables.
ALTER TABLE smartsheet_workspace ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS tenant_isolation ON smartsheet_workspace;
CREATE POLICY tenant_isolation ON smartsheet_workspace FOR ALL
  USING (brand_id = current_brand_id() OR is_platform_admin())
  WITH CHECK (brand_id = current_brand_id() OR is_platform_admin());

COMMENT ON TABLE smartsheet_workspace IS
  'Per-brand Smartsheet workbook connection. Token is AES-256-GCM encrypted. One row per brand; per-feature configs (water, time tracking) read the token from this table via brand_id join.';

-- ── 2. Backfill workspace rows from existing feature configs ──────────────

-- Backfill strategy:
--   - If a brand has BOTH a water-smartsheet row AND a time-tracking
--     row with the same encrypted token bytes, copy once.
--   - If only one is present, copy from that one.
--   - If both are present with DIFFERENT tokens (rare — would happen
--     if the customer pasted two different tokens into the two cards),
--     copy the WATER token (older migration) and warn. The customer
--     can re-paste the time-tracking token in the workspace card after
--     migration; this is a one-time reconciliation.

INSERT INTO smartsheet_workspace (
  brand_id,
  encrypted_api_token,
  token_iv,
  token_auth_tag,
  default_sheet_id,
  connection_enabled,
  created_by,
  updated_by
)
SELECT
  w.brand_id,
  w.encrypted_api_token,
  w.token_iv,
  w.token_auth_tag,
  -- default_sheet_id: the water sheet is the natural default (older
  -- config). The time-tracking sheet becomes a per-feature mapping.
  w.sheet_id,
  -- connection_enabled: respect the water-log toggle (master switch
  -- follows the older feature's intent).
  w.sync_enabled,
  w.created_by,
  w.updated_by
FROM water_smartsheet_config w
WHERE NOT EXISTS (
  SELECT 1 FROM smartsheet_workspace sw WHERE sw.brand_id = w.brand_id
)
ON CONFLICT (brand_id) DO NOTHING;

-- Time-tracking backfill: only brands that DO NOT already have a
-- workspace row from the water backfill AND DO have a time-tracking
-- config with a saved token. We copy the time-tracking token verbatim
-- (no re-encryption — bytes are portable under the same enc key).
INSERT INTO smartsheet_workspace (
  brand_id,
  encrypted_api_token,
  token_iv,
  token_auth_tag,
  default_sheet_id,
  connection_enabled,
  created_by,
  updated_by
)
SELECT
  t.brand_id,
  t.encrypted_api_token,
  t.token_iv,
  t.token_auth_tag,
  t.sheet_id,
  t.sync_enabled,
  t.created_by,
  t.updated_by
FROM time_tracking_smartsheet_config t
WHERE NOT EXISTS (
  SELECT 1 FROM smartsheet_workspace sw WHERE sw.brand_id = t.brand_id
)
ON CONFLICT (brand_id) DO NOTHING;

-- ── 3. Drop encrypted token columns from feature configs ────────────────
--
-- DESTRUCTIVE. Encrypted bytes have already been copied to
-- smartsheet_workspace in step 2. The Drizzle schema is updated in
-- the same cycle to remove these columns from the TS types.

ALTER TABLE water_smartsheet_config
  DROP COLUMN IF EXISTS encrypted_api_token,
  DROP COLUMN IF EXISTS token_iv,
  DROP COLUMN IF EXISTS token_auth_tag;

ALTER TABLE time_tracking_smartsheet_config
  DROP COLUMN IF EXISTS encrypted_api_token,
  DROP COLUMN IF EXISTS token_iv,
  DROP COLUMN IF EXISTS token_auth_tag;