import type { Sql } from 'postgres';

/**
 * Auto-create all Shade tables if they don't exist.
 *
 * Called on PostgresStorage / PostgresPrekeyStore construction.
 * Uses raw SQL (not Drizzle migrations) for zero-config deployment.
 *
 * All tables prefixed with `shade_` to avoid collisions when sharing
 * a PostgreSQL instance with another project.
 */
export async function ensureClientTables(sql: Sql): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS shade_identity (
      id INTEGER PRIMARY KEY CHECK (id = 1),
      signing_public_key TEXT NOT NULL,
      signing_private_key TEXT NOT NULL,
      dh_public_key TEXT NOT NULL,
      dh_private_key TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_config (
      key TEXT PRIMARY KEY,
      value TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_signed_prekeys (
      key_id INTEGER PRIMARY KEY,
      data_json TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_one_time_prekeys (
      key_id INTEGER PRIMARY KEY,
      data_json TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_sessions (
      address TEXT PRIMARY KEY,
      state_json TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_trusted_identities (
      address TEXT PRIMARY KEY,
      identity_key TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_retired_identities (
      id SERIAL PRIMARY KEY,
      data_json TEXT NOT NULL,
      retired_at BIGINT NOT NULL
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_retired_at_idx ON shade_retired_identities(retired_at)
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_stream_state (
      stream_id          TEXT PRIMARY KEY,
      direction          TEXT NOT NULL CHECK (direction IN ('send','receive')),
      peer_address       TEXT NOT NULL,
      status             TEXT NOT NULL CHECK (status IN ('active','paused','finished','aborted')),
      metadata_json      TEXT NOT NULL,
      partition_json     TEXT NOT NULL,
      lane_state_json    TEXT NOT NULL,
      io_descriptor_json TEXT NOT NULL,
      secret_enc         BYTEA NOT NULL,
      secret_nonce       BYTEA NOT NULL,
      overall_hash_state TEXT,
      created_at         BIGINT NOT NULL,
      updated_at         BIGINT NOT NULL
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_stream_state_peer_idx
    ON shade_stream_state(peer_address)
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_stream_state_updated_idx
    ON shade_stream_state(updated_at)
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_stream_state_status_idx
    ON shade_stream_state(status, direction)
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_peer_verifications (
      peer_address     TEXT PRIMARY KEY,
      fingerprint      TEXT NOT NULL,
      verified_at      BIGINT NOT NULL,
      verified_by      TEXT NOT NULL,
      identity_version BIGINT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_peer_identity_versions (
      peer_address TEXT PRIMARY KEY,
      version      BIGINT NOT NULL
    )
  `;
}

export async function ensurePrekeyServerTables(sql: Sql): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS shade_server_identities (
      address TEXT PRIMARY KEY,
      identity_signing_key TEXT NOT NULL,
      identity_dh_key TEXT NOT NULL,
      last_activity_at BIGINT NOT NULL DEFAULT 0
    )
  `;
  // Migrate existing deployments (no-op if column exists)
  await sql`
    ALTER TABLE shade_server_identities
    ADD COLUMN IF NOT EXISTS last_activity_at BIGINT NOT NULL DEFAULT 0
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_server_identities_activity_idx
    ON shade_server_identities(last_activity_at)
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_server_signed_prekeys (
      address TEXT PRIMARY KEY,
      key_id INTEGER NOT NULL,
      public_key TEXT NOT NULL,
      signature TEXT NOT NULL
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_server_one_time_prekeys (
      id SERIAL PRIMARY KEY,
      address TEXT NOT NULL,
      key_id INTEGER NOT NULL,
      public_key TEXT NOT NULL
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_server_otp_address_idx ON shade_server_one_time_prekeys(address)
  `;
}

/**
 * Tables for the Key-Transparency log (V3.12).
 *
 * Append-only invariant for `shade_kt_leaves`:
 *  - Application code never UPDATEs or DELETEs leaves.
 *  - A trigger guards against accidental mutation in misconfigured ops:
 *    even a misbehaving DBA query is rejected, which protects the log
 *    from silent re-writes.
 */
export async function ensureKTLogTables(sql: Sql): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS shade_kt_leaves (
      leaf_index   BIGINT PRIMARY KEY,
      leaf_hash    TEXT NOT NULL,
      timestamp_ms BIGINT NOT NULL,
      operation    SMALLINT NOT NULL,
      address      TEXT NOT NULL,
      bundle_hash  TEXT NOT NULL
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_kt_leaves_address_idx
    ON shade_kt_leaves(address)
  `;
  await sql`
    CREATE OR REPLACE FUNCTION shade_kt_block_mutations()
    RETURNS trigger AS $$
    BEGIN
      RAISE EXCEPTION 'shade_kt_leaves is append-only: % rejected', TG_OP;
    END;
    $$ LANGUAGE plpgsql
  `;
  await sql`DROP TRIGGER IF EXISTS shade_kt_leaves_no_update ON shade_kt_leaves`;
  await sql`
    CREATE TRIGGER shade_kt_leaves_no_update
    BEFORE UPDATE OR DELETE OR TRUNCATE ON shade_kt_leaves
    FOR EACH STATEMENT
    EXECUTE FUNCTION shade_kt_block_mutations()
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_kt_index (
      address           TEXT PRIMARY KEY,
      latest_leaf_index BIGINT NOT NULL,
      bundle_hash       TEXT NOT NULL,
      deleted           BOOLEAN NOT NULL DEFAULT FALSE
    )
  `;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_kt_sths (
      tree_size    BIGINT NOT NULL,
      timestamp_ms BIGINT NOT NULL,
      root_hash    TEXT NOT NULL,
      index_root   TEXT NOT NULL,
      log_id       TEXT NOT NULL,
      signature    TEXT NOT NULL,
      PRIMARY KEY (tree_size, timestamp_ms, signature)
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_kt_sths_timestamp_idx
    ON shade_kt_sths(timestamp_ms DESC)
  `;
}

/**
 * V4.9 — encrypted-blob primitive (`/v1/blob/<slotId>`). One row per
 * slot, keyed on the 64-hex slotId. ETag is a sequence value so it's
 * unique and monotonic across writers (matches the inbox `received_at`
 * pattern). The blob column holds base64-encoded AEAD ciphertext —
 * the relay never decrypts.
 */
export async function ensureBlobServerTables(sql: Sql): Promise<void> {
  await sql`CREATE SEQUENCE IF NOT EXISTS shade_blob_seq`;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_blob_slots (
      slot_id      TEXT PRIMARY KEY,
      owner_pubkey TEXT NOT NULL,
      blob         TEXT NOT NULL,
      etag         BIGINT NOT NULL,
      updated_at   BIGINT NOT NULL
    )
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_blob_updated_idx
    ON shade_blob_slots(updated_at)
  `;
}

export async function ensureInboxServerTables(sql: Sql): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS shade_inbox_owners (
      address     TEXT PRIMARY KEY,
      signing_key TEXT NOT NULL
    )
  `;
  await sql`CREATE SEQUENCE IF NOT EXISTS shade_inbox_seq`;
  await sql`
    CREATE TABLE IF NOT EXISTS shade_inbox_blobs (
      address     TEXT NOT NULL,
      msg_id      TEXT NOT NULL,
      ciphertext  TEXT NOT NULL,
      received_at BIGINT NOT NULL,
      expires_at  BIGINT NOT NULL,
      sender_fp   TEXT,
      PRIMARY KEY (address, msg_id)
    )
  `;
  // V4.8 — sender fingerprint column. Idempotent ADD COLUMN for live
  // databases that came up under a 4.7-or-earlier schema.
  await sql`
    ALTER TABLE shade_inbox_blobs
    ADD COLUMN IF NOT EXISTS sender_fp TEXT
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_inbox_addr_expires_idx
    ON shade_inbox_blobs(address, expires_at)
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_inbox_addr_received_idx
    ON shade_inbox_blobs(address, received_at)
  `;
  await sql`
    CREATE INDEX IF NOT EXISTS shade_inbox_expires_idx
    ON shade_inbox_blobs(expires_at)
  `;
}
