import {
  base58ToBytes,
  base64ToBytes,
  bytesToBase58,
  bytesToBase64,
  deriveEd25519FromMasterSecret,
  deriveMasterSecretFromPassword,
  publicKeyB64FromPkcs8Ed25519,
} from '../../js/services/crypto-utils.js';
import { formatSol, getBalanceSol } from '../../js/services/solana-wallet-service.js';

const LOGIN_RE = /^[a-z0-9_]{1,20}$/;
const ED25519_PKCS8_PREFIX = new Uint8Array([
  0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
]);

export function $(id) {
  return document.getElementById(id);
}

export function normalizeLogin(login) {
  return String(login || '').trim().toLowerCase();
}

export function validateLoginOrThrow(login) {
  const clean = normalizeLogin(login);
  if (!LOGIN_RE.test(clean)) {
    throw new Error('Логин должен содержать только a-z, 0-9, _ и быть длиной 1..20 символов');
  }
  return clean;
}

export function parseLoginList(text) {
  return String(text || '')
    .split(/\r?\n/)
    .map((value) => value.trim().toLowerCase())
    .filter(Boolean);
}

export function formatBigInt(value) {
  return BigInt(value || 0n).toString(10);
}

export function formatTimestamp(value) {
  const ts = Number(BigInt(value || 0n));
  if (!Number.isFinite(ts) || ts <= 0) return '—';
  return new Date(ts).toLocaleString('ru-RU');
}

export function setStatus(node, text, kind = 'info') {
  node.className = `status ${kind}`;
  node.textContent = String(text || '');
}

export function clearStatus(node) {
  node.className = 'status';
  node.textContent = '';
}

export function setGenMessage(node, text, kind) {
  node.className = `gen-msg ${kind}`;
  node.textContent = String(text || '');
}

export function clearGenMessage(node) {
  node.className = 'gen-msg';
  node.textContent = '';
}

export function setupPasswordEye(button, input) {
  button.addEventListener('click', () => {
    const nextType = input.type === 'password' ? 'text' : 'password';
    input.type = nextType;
    button.textContent = nextType === 'password' ? 'Показать' : 'Скрыть';
  });
}

function ensure32Bytes(bytes) {
  const input = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []);
  if (input.length > 32) throw new Error(`Ожидалось максимум 32 байта, получено ${input.length}`);
  if (input.length === 32) return input;
  const out = new Uint8Array(32);
  out.set(input, 32 - input.length);
  return out;
}

function pkcs8FromSeed32(seed32) {
  const seed = ensure32Bytes(seed32);
  const out = new Uint8Array(ED25519_PKCS8_PREFIX.length + seed.length);
  out.set(ED25519_PKCS8_PREFIX, 0);
  out.set(seed, ED25519_PKCS8_PREFIX.length);
  return out;
}

async function pairFromSeedBase58(seedB58, explicitPubB58) {
  const seed32 = ensure32Bytes(base58ToBytes(seedB58));
  const privatePkcs8B64 = bytesToBase64(pkcs8FromSeed32(seed32));
  const publicKeyB64 = await publicKeyB64FromPkcs8Ed25519(privatePkcs8B64);
  const actualPubB58 = bytesToBase58(base64ToBytes(publicKeyB64));
  const expectedPubB58 = String(explicitPubB58 || '').trim();
  if (expectedPubB58 && actualPubB58 !== expectedPubB58) {
    throw new Error(`Публичный ключ не совпадает с приватным seed: ${expectedPubB58}`);
  }
  return {
    publicKeyB64,
    privatePkcs8B64,
    publicKeyB58: actualPubB58,
    privateSeedB58: bytesToBase58(seed32),
  };
}

export async function buildKeyBundleFromForm(fieldMap, options = {}) {
  const requireBlockchain = options.requireBlockchain !== false;
  const root = await pairFromSeedBase58($(fieldMap.rootPriv).value, $(fieldMap.rootPub).value);
  const device = await pairFromSeedBase58($(fieldMap.devPriv).value, $(fieldMap.devPub).value);
  const blockchainPriv = String($(fieldMap.bchPriv).value || '').trim();
  const blockchainPub = String($(fieldMap.bchPub).value || '').trim();
  const hasBlockchainInput = Boolean(blockchainPriv || blockchainPub);
  let blockchain = null;
  if (requireBlockchain || hasBlockchainInput) {
    blockchain = await pairFromSeedBase58(blockchainPriv, blockchainPub);
  }
  return {
    keyBundle: {
      rootPair: { publicKeyB64: root.publicKeyB64, privatePkcs8B64: root.privatePkcs8B64 },
      blockchainPair: blockchain
        ? { publicKeyB64: blockchain.publicKeyB64, privatePkcs8B64: blockchain.privatePkcs8B64 }
        : null,
      devicePair: { publicKeyB64: device.publicKeyB64, privatePkcs8B64: device.privatePkcs8B64 },
    },
    normalized: {
      rootPubB58: root.publicKeyB58,
      rootPrivB58: root.privateSeedB58,
      bchPubB58: blockchain?.publicKeyB58 || '',
      bchPrivB58: blockchain?.privateSeedB58 || '',
      devPubB58: device.publicKeyB58,
      devPrivB58: device.privateSeedB58,
    },
  };
}

export async function deriveKeyBundleFromPassword({ login, password, onProgress }) {
  const cleanLogin = validateLoginOrThrow(login);
  const cleanPassword = String(password ?? '');
  if (!cleanPassword) throw new Error('Введите пароль');

  const masterSecret32 = await deriveMasterSecretFromPassword(cleanPassword, {
    login: cleanLogin,
    onProgress,
  });
  const [rootPair, blockchainPair, devicePair] = await Promise.all([
    deriveEd25519FromMasterSecret(masterSecret32, 'root.key'),
    deriveEd25519FromMasterSecret(masterSecret32, 'bch.key'),
    deriveEd25519FromMasterSecret(masterSecret32, 'dev.key'),
  ]);
  return {
    masterSecret32,
    keyBundle: { rootPair, blockchainPair, devicePair },
  };
}

export function fillKeyFields(fieldMap, keyBundle, masterSecret32) {
  if (masterSecret32) {
    $(fieldMap.masterSecret).value = bytesToBase58(masterSecret32);
  }
  $(fieldMap.rootPub).value = bytesToBase58(base64ToBytes(keyBundle.rootPair.publicKeyB64));
  $(fieldMap.rootPriv).value = bytesToBase58(base64ToBytes(keyBundle.rootPair.privatePkcs8B64).slice(-32));
  $(fieldMap.bchPub).value = bytesToBase58(base64ToBytes(keyBundle.blockchainPair.publicKeyB64));
  $(fieldMap.bchPriv).value = bytesToBase58(base64ToBytes(keyBundle.blockchainPair.privatePkcs8B64).slice(-32));
  $(fieldMap.devPub).value = bytesToBase58(base64ToBytes(keyBundle.devicePair.publicKeyB64));
  $(fieldMap.devPriv).value = bytesToBase58(base64ToBytes(keyBundle.devicePair.privatePkcs8B64).slice(-32));
}

export function updateSolAddress(fieldMap) {
  const box = $(fieldMap.solBox);
  const label = $(fieldMap.solAdr);
  const pubB58 = String($(fieldMap.devPub).value || '').trim();
  if (!pubB58) {
    box.classList.remove('show');
    label.textContent = '';
    return;
  }
  try {
    ensure32Bytes(base58ToBytes(pubB58));
    label.textContent = pubB58;
    box.classList.add('show');
  } catch {
    box.classList.remove('show');
    label.textContent = '';
  }
}

export function setText(idOrNode, value) {
  const node = typeof idOrNode === 'string' ? $(idOrNode) : idOrNode;
  if (node) node.textContent = String(value || '');
}

export function wireDeviceAddressPreview(fieldMap) {
  const update = () => updateSolAddress(fieldMap);
  $(fieldMap.devPub).addEventListener('input', update);
  update();
}

export function publicKeyBytesToBase58(value) {
  return bytesToBase58(value instanceof Uint8Array ? value : new Uint8Array(value || []));
}

export function compareExpectedPublicKeys(expected, actual) {
  const exp = String(expected || '').trim();
  const act = String(actual || '').trim();
  return {
    matches: Boolean(exp) && Boolean(act) && exp === act,
    expected: exp,
    actual: act,
  };
}

export function summarizeKeyComparison(resultMap) {
  const labels = {
    root: 'root',
    blockchain: 'blockchain',
    device: 'device',
  };
  const mismatches = Object.entries(resultMap)
    .filter(([, result]) => !result.matches)
    .map(([key]) => labels[key] || key);
  return {
    allMatch: mismatches.length === 0,
    mismatches,
  };
}

export async function refreshDeviceBalance({ endpoint, deviceAddress, targetNode }) {
  const cleanEndpoint = String(endpoint || '').trim();
  const cleanAddress = String(deviceAddress || '').trim();
  if (!cleanEndpoint) throw new Error('Укажите Solana endpoint');
  if (!cleanAddress) throw new Error('Сначала укажите device-адрес');
  const balance = await getBalanceSol({ endpoint: cleanEndpoint, address: cleanAddress });
  setText(targetNode, `Баланс device: ${formatSol(balance.sol, 6)} SOL (${balance.lamports} lamports)`);
  return balance;
}

export function buildDevnetTopupUrl(walletAddress) {
  const cleanWallet = String(walletAddress || '').trim();
  const url = new URL('../devnet-topup-view', window.location.href);
  if (cleanWallet) {
    url.searchParams.set('wallet', cleanWallet);
  }
  return url.toString();
}

export function openDevnetTopup(walletAddress) {
  const cleanWallet = String(walletAddress || '').trim();
  if (!cleanWallet) {
    throw new Error('Сначала укажите device-адрес');
  }
  window.open(buildDevnetTopupUrl(cleanWallet), '_blank', 'noopener');
}
