const crypto = require('crypto');
const { canonicalString, sha256 } = require('./hash');
const { envOrConfigString, getWalletConfig } = require('./config');

const WALLET_VERSION = 'mazex-wallet-1.0';
const WALLET_CONFIG = getWalletConfig().wallet || {};
const ADDRESS_PREFIX = envOrConfigString('MAZEX_ADDRESS_PREFIX', WALLET_CONFIG.address_prefix, 'mazex1');

function keyObjectFromPublicKey(publicKey) {
  return crypto.createPublicKey(publicKey);
}

function keyObjectFromPrivateKey(privateKey) {
  return crypto.createPrivateKey(privateKey);
}

function exportPublicKey(keyObject) {
  return keyObject.export({ type: 'spki', format: 'pem' });
}

function exportPrivateKey(keyObject) {
  return keyObject.export({ type: 'pkcs8', format: 'pem' });
}

function publicKeyFromPrivateKey(privateKey) {
  return exportPublicKey(crypto.createPublicKey(keyObjectFromPrivateKey(privateKey)));
}

function addressFromPublicKey(publicKey) {
  const key = keyObjectFromPublicKey(publicKey);
  const der = key.export({ type: 'spki', format: 'der' });
  const digest = crypto.createHash('sha256').update(der).digest('hex');
  return `${ADDRESS_PREFIX}${digest.slice(0, 40)}`;
}

function createWallet() {
  const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
  const publicKeyPem = exportPublicKey(publicKey);
  const privateKeyPem = exportPrivateKey(privateKey);
  return {
    version: WALLET_VERSION,
    address: addressFromPublicKey(publicKeyPem),
    public_key: publicKeyPem,
    private_key: privateKeyPem,
    algorithm: 'ed25519',
  };
}

function payloadHash(payload) {
  return sha256({
    wallet_version: WALLET_VERSION,
    payload,
  });
}

function signPayload({ privateKey, payload }) {
  const key = keyObjectFromPrivateKey(privateKey);
  const message = Buffer.from(canonicalString({ wallet_version: WALLET_VERSION, payload }), 'utf8');
  const signature = crypto.sign(null, message, key).toString('base64');
  const publicKey = publicKeyFromPrivateKey(privateKey);
  return {
    algorithm: 'ed25519',
    address: addressFromPublicKey(publicKey),
    public_key: publicKey,
    payload,
    payload_hash: payloadHash(payload),
    signature,
  };
}

function verifySignature({ publicKey, address, payload, signature }) {
  if (!publicKey || !address || !payload || !signature) {
    return {
      ok: false,
      code: 'SIGNATURE_FIELDS_REQUIRED',
      message: 'address, publicKey, payload, and signature are required',
    };
  }
  let expectedAddress;
  try {
    expectedAddress = addressFromPublicKey(publicKey);
  } catch (error) {
    return {
      ok: false,
      code: 'INVALID_PUBLIC_KEY',
      message: error.message || 'Invalid public key',
    };
  }
  if (expectedAddress !== address) {
    return {
      ok: false,
      code: 'ADDRESS_PUBLIC_KEY_MISMATCH',
      message: 'Address does not match public key',
      expected_address: expectedAddress,
    };
  }
  try {
    const message = Buffer.from(canonicalString({ wallet_version: WALLET_VERSION, payload }), 'utf8');
    const ok = crypto.verify(null, message, keyObjectFromPublicKey(publicKey), Buffer.from(signature, 'base64'));
    return ok
      ? {
          ok: true,
          address,
          payload_hash: payloadHash(payload),
        }
      : {
          ok: false,
          code: 'INVALID_SIGNATURE',
          message: 'Signature verification failed',
        };
  } catch (error) {
    return {
      ok: false,
      code: 'SIGNATURE_VERIFY_ERROR',
      message: error.message || 'Signature verification failed',
    };
  }
}

function answerHashForSigning(answer) {
  return sha256({
    u: String(answer?.u || ''),
    v: String(answer?.v || ''),
  });
}

function submitSigningPayload({
  chainId = 'mazexchain-local',
  layerNo,
  stepNo,
  stepCommit,
  answer,
  nonce = 0,
} = {}) {
  return {
    type: 'SUBMIT_ANSWER',
    chain_id: chainId,
    layer_no: Number(layerNo),
    step_no: Number(stepNo),
    step_commit: stepCommit,
    answer_hash: answerHashForSigning(answer),
    nonce: Math.max(0, Math.trunc(Number(nonce || 0))),
  };
}

module.exports = {
  ADDRESS_PREFIX,
  WALLET_VERSION,
  addressFromPublicKey,
  answerHashForSigning,
  createWallet,
  payloadHash,
  publicKeyFromPrivateKey,
  signPayload,
  submitSigningPayload,
  verifySignature,
};
