require('../config/loadEnv')();
const fs = require('fs');
const path = require('path');
const { Wallet, getAddress } = require('ethers');
const { protect, restrictAcl, unprotect } = require('../lib/operatorDpapi');
const operatorCustodyService = require('../services/operatorCustodyService');

const projectRoot = path.join(__dirname, '..');
const envPath = path.join(projectRoot, '.env');
const networkPath = path.join(projectRoot, 'runtime', 'plais-qbft', 'network.json');

function readJson(filePath) {
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

function canonicalIdentity(privateKey) {
  const derivedAddress = new Wallet(privateKey).address;
  const network = readJson(networkPath);
  const networkAddress = getAddress(network.operatorAddress);
  if (derivedAddress !== networkAddress) {
    throw Object.assign(new Error('Operator key does not match the active canonical network address.'), { code: 'OPERATOR_ADDRESS_MISMATCH' });
  }
  const genesis = readJson(path.resolve(network.genesisFile));
  const allocation = genesis.alloc && genesis.alloc[derivedAddress.slice(2).toLowerCase()];
  if (!allocation) {
    throw Object.assign(new Error('Operator address is absent from the canonical genesis allocation.'), { code: 'OPERATOR_GENESIS_ALLOCATION_MISSING' });
  }
  return derivedAddress;
}

function upsertEnv(source, key, value) {
  const line = `${key}=${value}`;
  const pattern = new RegExp(`^${key}=.*$`, 'm');
  if (pattern.test(source)) return source.replace(pattern, line);
  return `${source.replace(/\s*$/, '\n')}${line}\n`;
}

function updateEnvironment(operatorAddress) {
  let source = fs.readFileSync(envPath, 'utf8');
  const values = {
    PLAIS_EVM_OPERATOR_PRIVATE_KEY: '',
    PLAIS_EVM_OPERATOR_ADDRESS: operatorAddress,
    PLAIS_EVM_OPERATOR_CUSTODY_MODE: 'offline-dpapi',
    PLAIS_EVM_OPERATOR_SIGNING_ENABLED: '0',
    PLAIS_EVM_OPERATOR_VAULT_PATH: 'data/operator-custody/operator-key.dpapi.json',
    PLAIS_MULTICHAIN_TRANSFERS_ENABLED: '0'
  };
  for (const [key, value] of Object.entries(values)) source = upsertEnv(source, key, value);
  fs.writeFileSync(envPath, source, { encoding: 'utf8', mode: 0o600 });
  const written = fs.readFileSync(envPath, 'utf8');
  if (/^PLAIS_EVM_OPERATOR_PRIVATE_KEY=0x[0-9a-fA-F]{64}\s*$/m.test(written)) {
    throw Object.assign(new Error('Plaintext operator key remained after environment update.'), { code: 'PLAINTEXT_KEY_REMOVAL_FAILED' });
  }
}

function writeVault(privateKey, operatorAddress) {
  const vaultPath = operatorCustodyService.resolveVaultPath();
  const vaultDirectory = path.dirname(vaultPath);
  fs.mkdirSync(vaultDirectory, { recursive: true });
  restrictAcl(vaultDirectory, true);

  let ciphertext;
  if (fs.existsSync(vaultPath)) {
    const existing = readJson(vaultPath);
    const recovered = unprotect(existing.ciphertext);
    if (recovered !== privateKey) {
      throw Object.assign(new Error('Existing DPAPI vault contains a different operator key.'), { code: 'OPERATOR_VAULT_CONFLICT' });
    }
    ciphertext = existing.ciphertext;
  } else {
    ciphertext = protect(privateKey);
  }

  const metadata = {
    version: 1,
    protection: operatorCustodyService.EXPECTED_PROTECTION,
    operatorAddress,
    ciphertext,
    aclProtected: false,
    createdAt: new Date().toISOString()
  };
  fs.writeFileSync(vaultPath, `${JSON.stringify(metadata, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  const firstAudit = restrictAcl(vaultPath, false);
  if (!firstAudit.ok) throw Object.assign(new Error('Operator vault ACL verification failed.'), { code: 'OPERATOR_VAULT_ACL_FAILED' });
  metadata.aclProtected = true;
  fs.writeFileSync(vaultPath, `${JSON.stringify(metadata, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  const finalAudit = restrictAcl(vaultPath, false);
  if (!finalAudit.ok) throw Object.assign(new Error('Final operator vault ACL verification failed.'), { code: 'OPERATOR_VAULT_ACL_FAILED' });
  if (unprotect(readJson(vaultPath).ciphertext) !== privateKey) {
    throw Object.assign(new Error('DPAPI vault recovery self-test failed.'), { code: 'OPERATOR_VAULT_RECOVERY_FAILED' });
  }
  return finalAudit;
}

function main() {
  const privateKey = String(process.env.PLAIS_EVM_OPERATOR_PRIVATE_KEY || '').trim();
  if (!/^0x[0-9a-fA-F]{64}$/.test(privateKey)) {
    throw Object.assign(new Error('A valid plaintext operator key is required for the one-time migration.'), { code: 'OPERATOR_MIGRATION_KEY_MISSING' });
  }
  const operatorAddress = canonicalIdentity(privateKey);
  const acl = writeVault(privateKey, operatorAddress);
  updateEnvironment(operatorAddress);
  console.log(JSON.stringify({
    ok: true,
    custody: operatorCustodyService.EXPECTED_PROTECTION,
    operatorAddressMatchesCanonical: true,
    genesisAllocationVerified: true,
    plaintextKeyRemovedFromEnvironment: true,
    signingEnabled: false,
    multichainTransfersEnabled: false,
    aclProtected: acl.ok
  }));
}

try {
  main();
} catch (error) {
  console.error(JSON.stringify({ ok: false, error: error.code || 'OPERATOR_CUSTODY_MIGRATION_FAILED', message: error.message }));
  process.exit(1);
}
