import BaseDexie, { Table } from "dexie";
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
import * as R from "ramda";

import { Account, AccountsSchema } from "./tables/accounts";
import { Contact, ContactSchema } from "./tables/contacts";
import { Log, LogSchema } from "./tables/logs";
import {
  MASTER_SETTINGS_KEY,
  Settings,
  SettingsSchema,
} from "./tables/settings";
import { Temp, TempSchema } from "./tables/temp";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";

// Define types for tables that hold sensitive and non-sensitive data
type SensitiveTables = { accounts: Table<Account> };
type NonsensitiveTables = {
  contacts: Table<Contact>;
  logs: Table<Log>;
  settings: Table<Settings>;
  temp: Table<Temp>;
};

// Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
  BaseDexie & T;

// Initialize Dexie databases for sensitive and non-sensitive data
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;

// Manage the encryption key. If not present in localStorage, create and store it.
const secret =
  localStorage.getItem("secret") || Encryption.createRandomEncryptionKey();
if (!localStorage.getItem("secret")) localStorage.setItem("secret", secret);

// Apply encryption to the sensitive database using the secret key
encrypted(accountsDB, { secretKey: secret });

// Define the schemas for our databases
// Only the tables with index modifications need listing. https://dexie.org/docs/Tutorial/Design#database-versioning
accountsDB.version(1).stores(AccountsSchema);
// v1 also had contacts & settings
// v2 added Log
db.version(2).stores({
  ...ContactSchema,
  ...LogSchema,
  ...{ settings: "id" }, // old Settings schema
});
// v3 added Temp
db.version(3).stores(TempSchema);
db.version(4)
  .stores(SettingsSchema)
  .upgrade((tx) => {
    return tx
      .table("settings")
      .toCollection()
      .modify((settings) => {
        settings.accountDid = ""; // make it non-null for the default master settings, but still indexable
      });
  });

const DEFAULT_SETTINGS = {
  id: MASTER_SETTINGS_KEY,
  activeDid: undefined,
  apiServer: DEFAULT_ENDORSER_API_SERVER,
};

// Event handler to initialize the non-sensitive database with default settings
db.on("populate", async () => {
  await db.settings.add(DEFAULT_SETTINGS);
});

// retrieves default settings
// calls db.open()
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
  await db.open();
  return (await db.settings.get(MASTER_SETTINGS_KEY)) || DEFAULT_SETTINGS;
}

export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
  const defaultSettings = await retrieveSettingsForDefaultAccount();
  if (!defaultSettings.activeDid) {
    return defaultSettings;
  } else {
    const overrideSettings =
      (await db.settings
        .where("accountDid")
        .equals(defaultSettings.activeDid)
        .first()) || {};
    return R.mergeDeepRight(defaultSettings, overrideSettings);
  }
}

// Update settings for the given account, or in MASTER_SETTINGS_KEY if no accountDid is provided.
// Don't expose this because we should be explicit on whether we're updating the default settings or account settings.
async function updateSettings(settingsChanges: Settings): Promise<void> {
  await db.open();
  if (!settingsChanges.accountDid) {
    // ensure there is no "id" that would override the key
    delete settingsChanges.id;
    await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges);
  } else {
    const result = await db.settings
      .where("accountDid")
      .equals(settingsChanges.accountDid)
      .modify(settingsChanges);
    if (result === 0) {
      if (!settingsChanges.id) {
        // It is unfortunate that we have to set this explicitly.
        // We didn't make id a "++id" at the beginning and Dexie won't let us change it,
        // plus we made our first settings objects MASTER_SETTINGS_KEY = 1 instead of 0
        settingsChanges.id = (await db.settings.count()) + 1;
      }
      await db.settings.add(settingsChanges);
    }
  }
}

export async function updateDefaultSettings(settings: Settings): Promise<void> {
  delete settings.accountDid; // just in case
  await updateSettings(settings);
}

export async function updateAccountSettings(
  accountDid: string,
  settings: Settings,
): Promise<void> {
  settings.accountDid = accountDid;
  await updateSettings(settings);
}
