import { browser } from "$app/environment";
import { generateMnemonic, validateMnemonic } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english";
import {
  decrypt as nip49decrypt,
  encrypt as nip49encrypt,
} from "nostr-tools/nip49";
import type { Proof } from "@cashu/cashu-ts";
import Dexie from "dexie";
import type { Table } from "dexie";
import PortalBtcWallet, { type CashuStore } from "portalbtc-lib";
import { writable } from "svelte/store";

interface MetaEntry {
  key: string;
  value: string;
}

interface CashuTxn {
  txId: string;
  paymentType: "receive" | "send";
  amountSat: number;
  timestamp: number;
  status: "complete";
}

class CashuDB extends Dexie {
  proofs!: Table<Proof, string>;
  meta!: Table<MetaEntry, string>;
  txns!: Table<CashuTxn, string>;

  constructor() {
    super("cashu");
    this.version(1).stores({
      proofs: "&secret",
      meta: "&key",
    });
    this.version(2).stores({
      txns: "&txId",
    });
  }
}

export class DexieBasedCashuStore implements CashuStore {
  private readonly db: CashuDB = new CashuDB();

  async getProofs() {
    return this.db.proofs.toArray();
  }

  async getTxns() {
    return this.db.txns.toArray();
  }

  async getLastRedeemedCashuQuoteTimestamp() {
    const meta = await this.db.meta.get("lastRedeemedCashuQuoteTimestamp");
    return meta?.value ? parseInt(meta.value) : 0;
  }

  async setLastRedeemedCashuQuoteTimestamp(timestamp: number) {
    await this.db.meta.put({
      key: "lastRedeemedCashuQuoteTimestamp",
      value: timestamp.toString(),
    });
  }

  async persistProofs(proofs: Proof[]) {
    await this.db.proofs.clear();
    if (proofs.length) await this.db.proofs.bulkPut(proofs);
  }

  async persistTxns(txns: CashuTxn[]) {
    await this.db.txns.clear();
    if (txns.length) await this.db.txns.bulkPut(txns);
  }

  async persistLastRedeemedCashuQuoteTimestamp(timestamp: number) {
    await this.db.meta.put({
      key: "lastRedeemedCashuQuoteTimestamp",
      value: timestamp.toString(),
    });
  }
}

export const MNEMONIC_KEY = "seed";

export function createMnemonic(): string {
  return generateMnemonic(wordlist, 128);
}

export function isValidMnemonic(mnemonic: string): boolean {
  return validateMnemonic(mnemonic, wordlist);
}

export function isValidWord(word: string): boolean {
  return wordlist.includes(word.toLowerCase());
}

export async function encryptMnemonic(
  mnemonic: string,
  password: string,
): Promise<string> {
  const secretKey = new TextEncoder().encode(mnemonic);
  return nip49encrypt(secretKey, password);
}

export async function decryptMnemonic(
  data: string,
  password: string,
): Promise<string> {
  const decryptedBytes = nip49decrypt(data, password);
  return new TextDecoder().decode(decryptedBytes);
}

export function storeEncryptedMnemonic(encrypted: string) {
  if (browser) localStorage.setItem(MNEMONIC_KEY, encrypted);
}

export function hasMnemonic(): boolean {
  if (!browser) return false;
  return localStorage.getItem(MNEMONIC_KEY) !== null;
}

export const walletState = writable<
  | {
    open: false;
    wallet: undefined;
    mnemonic: undefined;
    balance: number;
    pendingBalance: number;
    cashuBalance: number;
  }
  | {
    open: true;
    mnemonic: string;
    wallet: PortalBtcWallet;
    balance: number;
    pendingBalance: number;
    cashuBalance: number;
  }
>({
  open: false,
  wallet: undefined,
  mnemonic: undefined,
  balance: 0,
  pendingBalance: 0,
  cashuBalance: 0,
});

export async function openWallet(mnemonic: string) {
  const wallet = await PortalBtcWallet.create(
    mnemonic,
    new DexieBasedCashuStore(),
    false,
    "mainnet",
  );
  wallet.addEventListener("balanceUpdated", () => {
    walletState.update((state) => ({
      ...state,
      balance: wallet.balance,
      pendingBalance: wallet.pendingBalance,
      cashuBalance: wallet.cashuBalance,
    }));
  });
  walletState.set({
    open: true,
    mnemonic,
    wallet,
    balance: wallet.balance,
    pendingBalance: wallet.pendingBalance,
    cashuBalance: wallet.cashuBalance,
  });
  let redeemCashuQuotesTimeout: ReturnType<typeof setTimeout>;
  async function redeemCashuQuotes() {
    await wallet.redeemCashuQuotes();
    let previousBalance = wallet.cashuBalance;
    if (previousBalance === wallet.cashuBalance) {
      walletState.update((state) => ({
        ...state,
        balance: wallet.balance,
        pendingBalance: wallet.pendingBalance,
        cashuBalance: wallet.cashuBalance,
      }));
    }
    redeemCashuQuotesTimeout = setTimeout(redeemCashuQuotes, 1000 * 5);
  }
  redeemCashuQuotes();
}
