import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import dotenv from 'dotenv';
import express from 'express';
import TronWebModule from 'tronweb';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.resolve(__dirname, '..', '.env') });

const TronWeb = TronWebModule.TronWeb || TronWebModule.default || TronWebModule;
const app = express();
app.use(express.json({ limit: '128kb' }));

const config = {
  port: Number(process.env.TRON_SIGNER_PORT || 8787),
  authToken: process.env.TRON_SIGNER_TOKEN || '',
  nodeUrl: process.env.TRON_NODE_URL || 'https://api.shasta.trongrid.io',
  tronGridApiKey: process.env.TRONGRID_API_KEY || '',
  usdtContract: process.env.TRON_USDT_CONTRACT_ADDRESS || '',
  keystoreFile: path.resolve(__dirname, '..', process.env.TRON_KEYSTORE_FILE || './data/keystore.json'),
  keystoreSecret: process.env.TRON_KEYSTORE_SECRET || '',
  defaultFromAddress: process.env.TRON_DEFAULT_FROM_ADDRESS || '',
  environment: process.env.TRON_ENVIRONMENT || 'sandbox',
};

if (!config.authToken || config.authToken.startsWith('change-me')) {
  console.warn('WARNING: TRON_SIGNER_TOKEN is not set to a secure value.');
}
if (!config.keystoreSecret || config.keystoreSecret.startsWith('change-me')) {
  console.warn('WARNING: TRON_KEYSTORE_SECRET is not set to a secure value.');
}

const headers = config.tronGridApiKey ? { 'TRON-PRO-API-KEY': config.tronGridApiKey } : {};
const tronWeb = new TronWeb({ fullHost: config.nodeUrl, headers });

function loadStore() {
  if (!fs.existsSync(config.keystoreFile)) {
    return { version: 1, addresses: {}, references: {}, transfers: {}, settings: {} };
  }
  const store = JSON.parse(fs.readFileSync(config.keystoreFile, 'utf8'));
  store.addresses ||= {};
  store.references ||= {};
  store.transfers ||= {};
  store.settings ||= {};
  return store;
}

function saveStore(store) {
  fs.mkdirSync(path.dirname(config.keystoreFile), { recursive: true });
  const tmp = `${config.keystoreFile}.tmp`;
  fs.writeFileSync(tmp, JSON.stringify(store, null, 2));
  fs.renameSync(tmp, config.keystoreFile);
}

function encryptionKey() {
  if (!config.keystoreSecret) throw new Error('TRON_KEYSTORE_SECRET is required');
  return crypto.createHash('sha256').update(config.keystoreSecret).digest();
}

function encryptPrivateKey(privateKey) {
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey(), iv);
  const encrypted = Buffer.concat([cipher.update(privateKey, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return {
    alg: 'aes-256-gcm',
    iv: iv.toString('base64'),
    tag: tag.toString('base64'),
    ciphertext: encrypted.toString('base64'),
  };
}

function decryptPrivateKey(payload) {
  const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey(), Buffer.from(payload.iv, 'base64'));
  decipher.setAuthTag(Buffer.from(payload.tag, 'base64'));
  return Buffer.concat([
    decipher.update(Buffer.from(payload.ciphertext, 'base64')),
    decipher.final(),
  ]).toString('utf8');
}

function requireAuth(req, res, next) {
  const header = req.header('authorization') || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : '';
  if (!config.authToken || token !== config.authToken) {
    return res.status(401).json({ error: 'UNAUTHORIZED' });
  }
  next();
}

function transferStatus(record) {
  return {
    id: record.id,
    status: record.status,
    tx_hash: record.tx_hash || null,
    created_at: record.created_at,
    updated_at: record.updated_at,
  };
}

app.get('/health', (_req, res) => {
  res.json({ ok: true, environment: config.environment, node_url: config.nodeUrl });
});

app.post('/wallet/validateaddress', requireAuth, (req, res) => {
  const address = String(req.body.address || '');
  const result = TronWeb.isAddress(address);
  res.json({ result, message: result ? 'valid' : 'invalid address' });
});

app.get('/v1/hot-wallet', requireAuth, (req, res) => {
  const store = loadStore();
  const address = config.defaultFromAddress || store.settings.default_from_address || null;
  res.json({ address, configured_by: config.defaultFromAddress ? 'env' : (address ? 'keystore' : null) });
});

app.post('/v1/hot-wallet', requireAuth, async (_req, res, next) => {
  try {
    const store = loadStore();
    const current = config.defaultFromAddress || store.settings.default_from_address;
    if (current && store.addresses[current]) {
      return res.json({ address: current, reused: true });
    }

    const account = await TronWeb.createAccount();
    const address = account.address.base58;
    store.addresses[address] = {
      address,
      reference: 'hot-wallet',
      network: 'tron',
      environment: config.environment,
      role: 'hot_wallet',
      private_key: encryptPrivateKey(account.privateKey),
      created_at: new Date().toISOString(),
    };
    store.settings.default_from_address = address;
    saveStore(store);

    res.status(201).json({ address, warning: 'Fund this address with TRX for fees and USDT/TRC20 before payouts can broadcast.' });
  } catch (error) {
    next(error);
  }
});
app.post('/v1/addresses', requireAuth, async (req, res, next) => {
  try {
    const reference = String(req.body.reference || '');
    const network = String(req.body.network || 'tron');
    const environment = String(req.body.environment || config.environment);
    if (network !== 'tron') return res.status(422).json({ error: 'UNSUPPORTED_NETWORK' });
    if (!reference) return res.status(422).json({ error: 'REFERENCE_REQUIRED' });

    const store = loadStore();
    if (store.references[reference]) {
      const existing = store.addresses[store.references[reference]];
      return res.json({ address: existing.address, reference, provider_reference: reference, reused: true });
    }

    const account = await TronWeb.createAccount();
    const address = account.address.base58;
    store.addresses[address] = {
      address,
      reference,
      network,
      environment,
      private_key: encryptPrivateKey(account.privateKey),
      created_at: new Date().toISOString(),
    };
    store.references[reference] = address;
    saveStore(store);

    res.status(201).json({ address, reference, provider_reference: reference });
  } catch (error) {
    next(error);
  }
});

app.post('/v1/transfers', requireAuth, async (req, res, next) => {
  try {
    const network = String(req.body.network || 'tron');
    const assetCode = String(req.body.asset_code || '').toUpperCase();
    const toAddress = String(req.body.to_address || '');
    const amountAtomic = String(req.body.amount_atomic || '');
    const idempotencyKey = String(req.body.idempotency_key || '');
    const preStore = loadStore();
    const fromAddress = String(req.body.from_address || config.defaultFromAddress || preStore.settings.default_from_address || '');

    if (network !== 'tron') return res.status(422).json({ error: 'UNSUPPORTED_NETWORK' });
    if (!['USDTTRC20', 'USDT'].includes(assetCode)) return res.status(422).json({ error: 'UNSUPPORTED_ASSET' });
    if (!config.usdtContract) return res.status(500).json({ error: 'TRON_USDT_CONTRACT_ADDRESS_REQUIRED' });
    if (!TronWeb.isAddress(toAddress)) return res.status(422).json({ error: 'INVALID_TO_ADDRESS' });
    if (!/^\d+$/.test(amountAtomic) || BigInt(amountAtomic) <= 0n) return res.status(422).json({ error: 'INVALID_AMOUNT' });
    if (!idempotencyKey) return res.status(422).json({ error: 'IDEMPOTENCY_KEY_REQUIRED' });
    if (!fromAddress) return res.status(422).json({ error: 'TRON_DEFAULT_FROM_ADDRESS_REQUIRED' });

    const store = loadStore();
    const existingId = Object.keys(store.transfers).find((id) => store.transfers[id].idempotency_key === idempotencyKey);
    if (existingId) return res.json(transferStatus(store.transfers[existingId]));

    const from = store.addresses[fromAddress];
    if (!from) return res.status(422).json({ error: 'FROM_ADDRESS_NOT_IN_KEYSTORE' });

    const privateKey = decryptPrivateKey(from.private_key);
    const functionSelector = 'transfer(address,uint256)';
    const options = { feeLimit: 100_000_000, callValue: 0 };
    const parameters = [
      { type: 'address', value: toAddress },
      { type: 'uint256', value: amountAtomic },
    ];

    const trigger = await tronWeb.transactionBuilder.triggerSmartContract(
      config.usdtContract,
      functionSelector,
      options,
      parameters,
      fromAddress,
    );

    if (!trigger.result || trigger.result.result !== true || !trigger.transaction) {
      return res.status(502).json({ error: 'TRIGGER_CONTRACT_FAILED', detail: trigger.result || null });
    }

    const signed = await tronWeb.trx.sign(trigger.transaction, privateKey);
    const broadcast = await tronWeb.trx.sendRawTransaction(signed);
    const txHash = broadcast.txid || signed.txID || null;
    const transfer = {
      id: `trn_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`,
      idempotency_key: idempotencyKey,
      status: broadcast.result ? 'confirming' : 'failed',
      tx_hash: txHash,
      broadcast,
      network,
      asset_code: assetCode,
      from_address: fromAddress,
      to_address: toAddress,
      amount_atomic: amountAtomic,
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    };
    store.transfers[transfer.id] = transfer;
    saveStore(store);

    res.status(broadcast.result ? 202 : 502).json(transferStatus(transfer));
  } catch (error) {
    next(error);
  }
});

app.get('/v1/transfers/:id', requireAuth, async (req, res) => {
  const store = loadStore();
  const record = store.transfers[req.params.id];
  if (!record) return res.status(404).json({ error: 'TRANSFER_NOT_FOUND' });

  if (record.tx_hash && ['confirming', 'pending'].includes(record.status)) {
    try {
      const info = await tronWeb.trx.getTransactionInfo(record.tx_hash);
      if (info && info.id) {
        const receipt = info.receipt || {};
        if (receipt.result === 'SUCCESS') {
          record.status = 'completed';
        } else if (receipt.result && receipt.result !== 'SUCCESS') {
          record.status = 'failed';
        }
        record.updated_at = new Date().toISOString();
        store.transfers[record.id] = record;
        saveStore(store);
      }
    } catch (_error) {
      // Keep confirming until TronGrid has indexed the transaction or returns a terminal status.
    }
  }

  res.json(transferStatus(record));
});

app.use((error, _req, res, _next) => {
  console.error(error);
  res.status(500).json({ error: 'SIGNER_ERROR', message: error.message });
});

app.listen(config.port, () => {
  console.log(`TRON signer listening on http://127.0.0.1:${config.port}`);
});
