import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from 'bip39';
import { BIP32Factory } from 'bip32';
import * as ecc from 'tiny-secp256k1';
const bip32 = BIP32Factory(ecc);
import { payments, networks } from 'bitcoinjs-lib';
import { Wallet as EthWallet } from '@ethereumjs/wallet';
import { Keypair as SolKeypair } from '@solana/web3.js';
import bs58 from 'bs58';
import crypto from 'crypto';
import { bech32 } from 'bech32';
import xrpl from 'xrpl';
import hdkey from 'hdkey';
import { deriveAddress, deriveKeypair } from 'ripple-keypairs';
import elliptic from 'elliptic';
import bs58check from 'bs58check';
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import StellarSdk from 'stellar-sdk';
import { Wallet } from '@ethereumjs/wallet';
import { mnemonicToWalletKey } from 'ton-crypto';
import { Address } from '@ton/core';
import { mnemonicToMiniSecret } from '@polkadot/util-crypto';
import { Keyring } from '@polkadot/keyring';

const ec = new elliptic.ec('secp256k1');

class MultiCoinAddressGenerator {
  constructor(mnemonic, depth = 1) {
    if (!this._isValidMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
    this.mnemonic = mnemonic.trim();
    this.depth = depth;

    this.coins = {
      RVN: { type: 'bip44', coinType: 175, versionByte: 0x3c },
      DGB: { type: 'bip44', coinType: 20, versionByte: 0x1E },
      QTUM: { type: 'custom_base58', coinType: 2301, versionByte: 0x3a },
      FIRO: { type: 'custom_base58', coinType: 136, versionByte: 0x52 },
      GRS: { type: 'bech32', coinType: 17, prefix: 'grs', derivation: 'bip84' },
      TT: { type: 'ethereum', coinType: 1001 },
      GO: { type: 'ethereum', coinType: 6060 },
      CRO: { type: 'cosmos', coinType: 394, prefix: 'cro' },
      BLD: { type: 'cosmos', coinType: 564, prefix: 'agoric' },
      RUNE: { type: 'cosmos', coinType: 931, prefix: 'thor' },
      AKT: { type: 'cosmos', coinType: 118, prefix: 'akash' },
      SEI: { type: 'cosmos', coinType: 118, prefix: 'sei' },
      JUNO: { type: 'cosmos', coinType: 118, prefix: 'juno' },
      STRD: { type: 'cosmos', coinType: 118, prefix: 'stride' },
      SUI: { type: 'sui', coinType: 784 },
      TRX: { type: 'tron', coinType: 195 },
      ATOM: { type: 'cosmos', coinType: 118, prefix: 'cosmos' },
      BNB: { type: 'ethereum', coinType: 60 },
      BCH: { type: 'bip44', coinType: 145, versionByte: 0x00 },
      XLM: { type: 'stellar', coinType: 148 },
      TON: { type: 'ton', coinType: 607 },
      DOT: { type: 'polkadot', coinType: 354 },
    };
  }

  _isValidMnemonic(mnemonic) {
    const wordCount = mnemonic.trim().split(/\s+/).length;
    return [12, 15, 18, 21, 24].includes(wordCount);
  }

  _deriveSeed() {
    return mnemonicToSeedSync(this.mnemonic);
  }

  _derivePrivateKey(seed, coinInfo, depthIndex) {
    const purpose = coinInfo.derivation === 'bip84' ? 84 : 44;
    const hardened = 0x80000000;
    const path = [
      purpose + hardened,
      coinInfo.coinType + hardened,
      0 + hardened,
      0,
      depthIndex,
    ];
    let hdkeyNode = hdkey.fromMasterSeed(seed);
    path.forEach((index) => {
      hdkeyNode = hdkeyNode.deriveChild(index);
    });
    return hdkeyNode.privateKey.toString('hex');
  }

  _generateEthereumAddress(privKeyHex) {
    const privKeyBuffer = Buffer.from(privKeyHex, 'hex');
    const wallet = Wallet.fromPrivateKey(privKeyBuffer);
    return wallet.getChecksumAddressString(); // returns 0x-prefixed address with checksums
  }
  async _generateBase58Address(privKeyBytes, versionByte) {
    const key = ec.keyFromPrivate(privKeyBytes);
    const pubKey = key.getPublic(true, 'hex');
    const pubKeyBuffer = Buffer.from(pubKey, 'hex');
    const sha256 = crypto.createHash('sha256').update(pubKeyBuffer).digest();
    const ripemd160 = crypto.createHash('ripemd160').update(sha256).digest();
    const payload = Buffer.concat([Buffer.from([versionByte]), ripemd160]);
    return bs58check.encode(payload);
  }

  async _generateBech32Address(privKeyBytes, prefix) {
    const key = ec.keyFromPrivate(privKeyBytes);
    const pubKey = key.getPublic(true, 'hex');
    const pubKeyBuffer = Buffer.from(pubKey, 'hex');
    const sha256 = crypto.createHash('sha256').update(pubKeyBuffer).digest();
    const ripemd160 = crypto.createHash('ripemd160').update(sha256).digest();
    const words = bech32.toWords(ripemd160);
    return bech32.encode(prefix, words);
  }

  _generateRippleAddress(seedHex) {
    const keypair = deriveKeypair(seedHex);
    return deriveAddress(keypair.publicKey);
  }

  _generateSuiAddress(depthIndex = 0) {
    const derivationPath = `m/44'/784'/0'/0'/${depthIndex}'`;
    const keypair = Ed25519Keypair.deriveKeypair(this.mnemonic, derivationPath);
    return {
      address: keypair.getPublicKey().toSuiAddress(),
      privKeyHex: Buffer.from(keypair.getSecretKey()).toString('hex'),
    };
  }

  _generateTronAddress(privKeyHex) {
    const ecKey = ec.keyFromPrivate(Buffer.from(privKeyHex, 'hex'));
    const pubKey = ecKey.getPublic(true, 'hex');
    const pubKeyBytes = Buffer.from(pubKey, 'hex');
    const { keccak256 } = require('js-sha3');
    const hash = Buffer.from(keccak256.arrayBuffer(pubKeyBytes));
    const addressBytes = Buffer.concat([Buffer.from([0x41]), hash.slice(-20)]);
    return bs58check.encode(addressBytes);
  }

  _generateStellarAddress(privKeyBytes) {
    const keypair = StellarSdk.Keypair.fromRawEd25519Seed(privKeyBytes);
    return keypair.publicKey();
  }

  _generateCosmosAddress(privKeyBytes, prefix) {
    return this._generateBech32Address(privKeyBytes, prefix);
  }

  async _generateTonAddress() {
    try {
      const mnemonicWords = this.mnemonic.split(' ');
      const keyPair = await mnemonicToWalletKey(mnemonicWords);

      if (!keyPair || !keyPair.publicKey) {
        throw new Error('Failed to derive TON key pair from mnemonic');
      }
      const workChain = 0; // 0 for mainnet user accounts
      const address = new Address(workChain, Buffer.from(keyPair.publicKey));

      const friendlyAddress = address.toString({ bounceable: true, testOnly: false });
      return {
        address: friendlyAddress.toString(),
        privKeyHex: Buffer.from(keyPair.secretKey).toString('hex'),
      };
    } catch (err) {
      console.log(err)
    }
  }

  _generatePolkadotAddress() {
    try {
      const seed = mnemonicToMiniSecret(this.mnemonic);
      const keyring = new Keyring({ type: 'sr25519' });
      const pair = keyring.addFromSeed(seed);
      const privKeyUint8Array = pair.secretKey || pair.encodePkcs8();
      const privKeyHex = Buffer.from(privKeyUint8Array).toString('hex');

      return {
        address: pair.address,
        privKeyHex,
      };
    } catch (err) {
      console.log(err)
    }
  }

  async generateCoinAddress(coin, depthIndex = 0) {
    if (!this.coins[coin]) throw new Error(`Coin ${coin} not supported`);
    const coinInfo = this.coins[coin];
    const seed = this._deriveSeed();

    if (coin === 'SUI') {
      return this._generateSuiAddress(depthIndex);
    }

    let privKeyHex = this._derivePrivateKey(seed, coinInfo, depthIndex);
    const privKeyBytes = Buffer.from(privKeyHex, 'hex');
    let address;

    switch (coinInfo.type) {
      case 'ethereum':
        address = this._generateEthereumAddress(privKeyHex);
        break;
      case 'bip44': // BCH, DGB, RVN
      case 'custom_base58': // QTUM, FIRO
        address = await this._generateBase58Address(privKeyBytes, coinInfo.versionByte);
        break;
      case 'bech32': // GRS
      case 'cosmos': // ATOM, CRO, BLD, RUNE, AKT, SEI, JUNO, STRD
        address = await this._generateCosmosAddress(privKeyBytes, coinInfo.prefix);
        break;
      case 'ripple':
        address = this._generateRippleAddress(privKeyHex);
        break;
      case 'tron':
        address = this._generateTronAddress(privKeyHex);
        break;
      case 'stellar':
        address = this._generateStellarAddress(privKeyBytes);
        break;
      case 'ton':
        const res = await this._generateTonAddress();
        address = res.address;
        privKeyHex = res.privKeyHex;
        break;
      case 'polkadot':
        const resz = this._generatePolkadotAddress();
        address = resz.address;
        privKeyHex = resz.privKeyHex;
        break;

      default:
        throw new Error('Unsupported coin type for address generation');
    }

    return { address, privKeyHex };
  }

  async generate(enabledCoins = null) {
    if (!enabledCoins) enabledCoins = Object.keys(this.coins);
    const results = {};
    for (const coin of enabledCoins) {
      try {
        results[coin] = await this.generateCoinAddress(coin, 0);
      } catch (e) {
        results[coin] = { error: e.message };
      }
    }
    return results;
  }
}

function generateDogecoin(mnemonic, account = 0, change = 0, index = 0) {
  if (!validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
  const seed = mnemonicToSeedSync(mnemonic);
  const root = bip32.fromSeed(seed);
  const addresses = [];
  const wifs = [];

  for (let a = 0; a <= account; a++) {
    for (let c = 0; c <= change; c++) {
      for (let i = 0; i <= index; i++) {
        const path = `m/44'/3'/${a}'/${c}/${i}`;
        const child = root.derivePath(path);
        const priv = child.privateKey;
        const pub = child.publicKey;

        // Dogecoin P2PKH address
        const sha256hash = crypto.createHash('sha256').update(pub).digest();
        const ripemd160hash = crypto.createHash('ripemd160').update(sha256hash).digest();
        const version = Buffer.from([0x1E]);
        const payload = Buffer.concat([version, ripemd160hash]);
        const checksum = crypto
          .createHash('sha256')
          .update(crypto.createHash('sha256').update(payload).digest())
          .digest()
          .slice(0, 4);
        const address = bs58.encode(Buffer.concat([payload, checksum]));

        // WIF
        const wifVersion = Buffer.from([0x9E]);
        const wifExtended = Buffer.concat([wifVersion, priv, Buffer.from([0x01])]);
        const wifChecksum = crypto
          .createHash('sha256')
          .update(crypto.createHash('sha256').update(wifExtended).digest())
          .digest()
          .slice(0, 4);
        const wif = bs58.encode(Buffer.concat([wifExtended, wifChecksum]));

        addresses.push(address);
        wifs.push(wif);
      }
    }
  }

  return { addresses, wifs };
}

function generateLitecoin(mnemonic, account = 0, change = 0, index = 0) {
  if (!validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
  const seed = mnemonicToSeedSync(mnemonic);
  const root = bip32.fromSeed(seed);
  const addresses = { legacy: [], segwit: [], nativeSegwit: [] };
  const wifs = [];

  for (let a = 0; a <= account; a++) {
    for (let c = 0; c <= change; c++) {
      for (let i = 0; i <= index; i++) {
        // Legacy P2PKH
        let path = `m/44'/2'/${a}'/${c}/${i}`;
        let child = root.derivePath(path);
        const pub = child.publicKey;
        const priv = child.privateKey;
        const sha256hash = crypto.createHash('sha256').update(pub).digest();
        const ripemd160hash = crypto.createHash('ripemd160').update(sha256hash).digest();
        const version = Buffer.from([0x30]);
        const payload = Buffer.concat([version, ripemd160hash]);
        const checksum = crypto
          .createHash('sha256')
          .update(crypto.createHash('sha256').update(payload).digest())
          .digest()
          .slice(0, 4);
        const addressLegacy = bs58.encode(Buffer.concat([payload, checksum]));
        addresses.legacy.push(addressLegacy);

        // SegWit P2SH
        path = `m/49'/2'/${a}'/${c}/${i}`;
        child = root.derivePath(path);
        const redeemScript = Buffer.concat([Buffer.from([0x00, 0x14]), ripemd160hash]);
        const redeemScriptHash = crypto.createHash('ripemd160').update(crypto.createHash('sha256').update(redeemScript).digest()).digest();
        const p2shPayload = Buffer.concat([Buffer.from([0x32]), redeemScriptHash]);
        const checksum2 = crypto
          .createHash('sha256')
          .update(crypto.createHash('sha256').update(p2shPayload).digest())
          .digest()
          .slice(0, 4);
        addresses.segwit.push(bs58.encode(Buffer.concat([p2shPayload, checksum2])));

        // Native SegWit (Bech32)
        path = `m/84'/2'/${a}'/${c}/${i}`;
        child = root.derivePath(path);
        const words = bech32.toWords(ripemd160hash);
        addresses.nativeSegwit.push(bech32.encode('ltc', 0, words));

        // WIF
        const wifVersion = Buffer.from([0xB0]);
        const wifExtended = Buffer.concat([wifVersion, priv, Buffer.from([0x01])]);
        const wifChecksum = crypto
          .createHash('sha256')
          .update(crypto.createHash('sha256').update(wifExtended).digest())
          .digest()
          .slice(0, 4);
        wifs.push(bs58.encode(Buffer.concat([wifExtended, wifChecksum])));
      }
    }
  }

  return { addresses, wifs };
}

function generateXRP(mnemonic) {
  if (!validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
  try {
    const wallet = xrpl.Wallet.fromMnemonic(mnemonic);
    return { address: wallet.address, privKey: wallet.privateKey };
  } catch (error) {
    console.error('Error generating XRP wallet:', error);
    throw error;
  }
}

async function generateWallets(inputMnemonic = null) {
  const mnemonic = inputMnemonic || generateMnemonic(128);

  if (!validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');

  const seed = mnemonicToSeedSync(mnemonic);

  // Bitcoin
  const btcRoot = bip32.fromSeed(seed);
  const btcChild = btcRoot.derivePath("m/84'/0'/0'/0/0");
  const pubkeyBuffer = Buffer.from(btcChild.publicKey);
  const { address: btcAddress } = payments.p2wpkh({
    pubkey: pubkeyBuffer,
    network: networks.bitcoin,
  });
  const btcPrivateKeyWIF = btcChild.toWIF();

  // Ethereum (also for BNB BSC)
  const ethChild = bip32.fromSeed(seed).derivePath("m/44'/60'/0'/0/0");
  const ethWallet = EthWallet.fromPrivateKey(ethChild.privateKey);
  const ethAddress = ethWallet.getAddressString();
  const ethPrivateKey = ethWallet.getPrivateKeyString();

  // Solana
  const secpRoot = bip32.fromSeed(seed);
  const secpChild = secpRoot.derivePath("m/44'/501'/0'/0/0");
  const edSeed = secpChild.privateKey.slice(0, 32);
  const solKeypair = SolKeypair.fromSeed(edSeed);
  const solAddress = solKeypair.publicKey.toBase58();
  const solPrivateKey = bs58.encode(solKeypair.secretKey);

  // Dogecoin
  const doge = generateDogecoin(mnemonic, 0, 0, 0);

  // Litecoin
  const ltc = generateLitecoin(mnemonic, 0, 0, 0);

  // XRP
  const xrp = generateXRP(mnemonic);

  // Multi-coin
  const generator = new MultiCoinAddressGenerator(mnemonic);
  const enabledCoins = [
    'SUI',
    'BNB',
    'TON',
    'DOT',
    'ATOM',
    'BCH',
    'XLM',
    'TRX',
    'RVN',
    'DGB',
    'BLD',
    'RUNE',
    'AKT',
    'SEI',
    'JUNO',
    'STRD',
  ];
  const multiWallets = await generator.generate(enabledCoins);

  function mapDerivationPath(coin) {
    return {
      RVN: "m/44'/175'/0'/0/0",
      DGB: "m/84'/20'/0'/0/0",
      QTUM: "m/44'/2301'/0'/0/0",
      FIRO: "m/44'/136'/0'/0/0",
      GRS: "m/84'/17'/0'/0/0",
      CRO: "m/44'/394'/0'/0/0",
      BLD: "m/44'/564'/0'/0/0",
      RUNE: "m/44'/931'/0'/0/0",
      AKT: "m/44'/118'/0'/0/0",
      SEI: "m/44'/118'/0'/0/0",
      JUNO: "m/44'/118'/0'/0/0",
      STRD: "m/44'/118'/0'/0/0",
      SUI: "m/44'/784'/0'/0/0",
      TRX: "m/44'/195'/0'/0/0",
      ATOM: "m/44'/118'/0'/0/0",
      BNB: "m/44'/60'/0'/0/0", // changed to 60 for BSC (Ethereum derivation)
      BCH: "m/44'/145'/0'/0/0",
      XLM: "m/44'/148'/0'/0/0",
      TON: "",
      DOT: ""
    }[coin] || "";
  }

  const formattedMultiWallets = {};
  for (const [coin, walletInfo] of Object.entries(multiWallets)) {
    if (walletInfo.error) continue; // skip errored coins
    formattedMultiWallets[coin.toLowerCase()] = {
      derivationPath: mapDerivationPath(coin),
      address: walletInfo.address,
      privateKey: walletInfo.privKeyHex,
    };
  }

  const wallets = {
    btc: {
      derivationPath: "m/84'/0'/0'/0/0",
      address: btcAddress,
      privateKey: btcPrivateKeyWIF,
    },
    eth: {
      derivationPath: "m/44'/60'/0'/0/0",
      address: ethAddress,
      privateKey: ethPrivateKey,
    },
    sol: {
      derivationPath: "m/44'/501'/0'/0/0",
      address: solAddress,
      privateKey: solPrivateKey,
    },
    doge: {
      derivationPath: "m/44'/3'/0'/0/0",
      address: doge.addresses[0],
      privateKey: doge.wifs[0],
    },
    ltc: {
      derivationPath: "m/44'/2'/0'/0/0",
      address: ltc.addresses.legacy[0],
      privateKey: ltc.wifs[0],
    },
    xrp: {
      derivationPath: "m/44'/144'/0'/0'/0'",
      address: xrp.address,
      privateKey: xrp.privKey,
    },
    ...formattedMultiWallets,
  };

  const totalAddresses = Object.keys(wallets).length;

  return {
    seedPhrase: mnemonic,
    wallets,
    total: totalAddresses,
  };
}

// // Export or example usage:

// // To generate new wallets (with new mnemonic)
// generateWallets().then(console.log).catch(console.error);

export { generateWallets, MultiCoinAddressGenerator, generateDogecoin, generateLitecoin, generateXRP };
