import crypto from "node:crypto";
import { CryptoNetwork } from "@prisma/client";
import { HDNodeWallet } from "ethers";
import BIP32Factory from "bip32";
import * as bip39 from "bip39";
import * as bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { prisma } from "@/server/db";
import { ProviderConfigurationError } from "@/server/errors";
import { env } from "@/server/env";

const bip32 = BIP32Factory(ecc);

type DerivedAddress = {
  address: string;
  derivationPath: string;
  publicKey?: string;
};

export async function ensureUserCryptoAddresses(userId: string) {
  const networks = [CryptoNetwork.BITCOIN, CryptoNetwork.ETHEREUM];
  const addresses = await Promise.all(
    networks.map(async (network) => {
      const existing = await prisma.cryptoDepositAddress.findUnique({
        where: { userId_network: { userId, network } }
      });

      if (existing) {
        return existing;
      }

      const derived = await deriveDepositAddress(userId, network);
      return prisma.cryptoDepositAddress.create({
        data: {
          userId,
          network,
          address: derived.address,
          derivationPath: derived.derivationPath,
          publicKey: derived.publicKey
        }
      });
    })
  );

  return addresses;
}

export async function deriveDepositAddress(userId: string, network: CryptoNetwork): Promise<DerivedAddress> {
  const config = env();
  const mnemonic = config.CRYPTO_WALLET_MNEMONIC.trim();

  if (!bip39.validateMnemonic(mnemonic)) {
    throw new ProviderConfigurationError("Invalid crypto wallet mnemonic");
  }

  const index = userDerivationIndex(userId);

  if (network === CryptoNetwork.ETHEREUM) {
    const derivationPath = `m/44'/60'/0'/0/${index}`;
    const wallet = HDNodeWallet.fromPhrase(mnemonic, undefined, derivationPath);
    return {
      address: wallet.address,
      derivationPath,
      publicKey: wallet.publicKey
    };
  }

  const derivationPath = `m/84'/0'/0'/0/${index}`;
  const seed = await bip39.mnemonicToSeed(mnemonic);
  const root = bip32.fromSeed(seed, bitcoin.networks.bitcoin);
  const child = root.derivePath(derivationPath);
  const payment = bitcoin.payments.p2wpkh({
    pubkey: Buffer.from(child.publicKey),
    network: bitcoin.networks.bitcoin
  });

  if (!payment.address) {
    throw new ProviderConfigurationError("Unable to derive Bitcoin deposit address");
  }

  return {
    address: payment.address,
    derivationPath,
    publicKey: Buffer.from(child.publicKey).toString("hex")
  };
}

function userDerivationIndex(userId: string) {
  const digest = crypto.createHash("sha256").update(userId).digest();
  return digest.readUInt32BE(0) & 0x7fffffff;
}
