import {
  registerMigration,
  runMigrations as runMigrationsService,
} from "../services/migrationService";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
import { arrayBufferToBase64 } from "@/libs/crypto";

// Generate a random secret for the secret table

// It's not really secure to maintain the secret next to the user's data.
// However, until we have better hooks into a real wallet or reliable secure
// storage, we'll do this for user convenience. As they sign more records
// and integrate with more people, they'll value it more and want to be more
// secure, so we'll prompt them to take steps to back it up, properly encrypt,
// etc. At the beginning, we'll prompt for a password, then we'll prompt for a
// PWA so it's not in a browser... and then we hope to be integrated with a
// real wallet or something else more secure.

// One might ask: why encrypt at all? We figure a basic encryption is better
// than none. Plus, we expect to support their own password or keystore or
// external wallet as better signing options in the future, so it's gonna be
// important to have the structure where each account access might require
// user action.

// (Once upon a time we stored the secret in localStorage, but it frequently
// got erased, even though the IndexedDB still had the identity data. This
// ended up throwing lots of errors to the user... and they'd end up in a state
// where they couldn't take action because they couldn't unlock that identity.)

const randomBytes = crypto.getRandomValues(new Uint8Array(32));
const secretBase64 = arrayBufferToBase64(randomBytes);

// Each migration can include multiple SQL statements (with semicolons)
const MIGRATIONS = [
  {
    name: "001_initial",
    sql: `
      CREATE TABLE IF NOT EXISTS accounts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        dateCreated TEXT NOT NULL,
        derivationPath TEXT,
        did TEXT NOT NULL,
        identityEncrBase64 TEXT, -- encrypted & base64-encoded
        mnemonicEncrBase64 TEXT, -- encrypted & base64-encoded
        passkeyCredIdHex TEXT,
        publicKeyHex TEXT NOT NULL
      );

      CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);

      CREATE TABLE IF NOT EXISTS secret (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        secretBase64 TEXT NOT NULL
      );

      INSERT INTO secret (id, secretBase64) VALUES (1, '${secretBase64}');

      CREATE TABLE IF NOT EXISTS settings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        accountDid TEXT,
        activeDid TEXT,
        apiServer TEXT,
        filterFeedByNearby BOOLEAN,
        filterFeedByVisible BOOLEAN,
        finishedOnboarding BOOLEAN,
        firstName TEXT,
        hideRegisterPromptOnNewContact BOOLEAN,
        isRegistered BOOLEAN,
        lastName TEXT,
        lastAckedOfferToUserJwtId TEXT,
        lastAckedOfferToUserProjectsJwtId TEXT,
        lastNotifiedClaimId TEXT,
        lastViewedClaimId TEXT,
        notifyingNewActivityTime TEXT,
        notifyingReminderMessage TEXT,
        notifyingReminderTime TEXT,
        partnerApiServer TEXT,
        passkeyExpirationMinutes INTEGER,
        profileImageUrl TEXT,
        searchBoxes TEXT, -- Stored as JSON string
        showContactGivesInline BOOLEAN,
        showGeneralAdvanced BOOLEAN,
        showShortcutBvc BOOLEAN,
        vapid TEXT,
        warnIfProdServer BOOLEAN,
        warnIfTestServer BOOLEAN,
        webPushServer TEXT
      );

      CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid);

      INSERT INTO settings (id, apiServer) VALUES (1, '${DEFAULT_ENDORSER_API_SERVER}');

      CREATE TABLE IF NOT EXISTS contacts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        did TEXT NOT NULL,
        name TEXT,
        contactMethods TEXT, -- Stored as JSON string
        nextPubKeyHashB64 TEXT,
        notes TEXT,
        profileImageUrl TEXT,
        publicKeyBase64 TEXT,
        seesMe BOOLEAN,
        registered BOOLEAN
      );

      CREATE INDEX IF NOT EXISTS idx_contacts_did ON contacts(did);
      CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name);

      CREATE TABLE IF NOT EXISTS logs (
        date TEXT NOT NULL,
        message TEXT NOT NULL
      );

      CREATE TABLE IF NOT EXISTS temp (
        id TEXT PRIMARY KEY,
        blobB64 TEXT
      );
      `,
  },
  {
    name: "002_add_iViewContent_to_contacts",
    sql: `
      ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
    `,
  },
];

/**
 * @param sqlExec - A function that executes a SQL statement and returns the result
 * @param extractMigrationNames - A function that extracts the names (string array) from "select name from migrations"
 */
export async function runMigrations<T>(
  sqlExec: (sql: string, params?: unknown[]) => Promise<unknown>,
  sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
  extractMigrationNames: (result: T) => Set<string>,
): Promise<void> {
  for (const migration of MIGRATIONS) {
    registerMigration(migration);
  }
  await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
}
