import axios from 'axios';
import { Interface, parseUnits } from 'ethers';

export type AlchemyNetwork = 'eth-mainnet' | 'defi-oracle-meta-mainnet';

const ERC20_ABI = [
  'function balanceOf(address) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function transfer(address to, uint256 amount) returns (bool)',
];

function alchemyKey(override?: string): string {
  const key = override?.trim() || process.env.ALCHEMY_API_KEY?.trim();
  if (!key) {
    throw new Error('ALCHEMY_API_KEY unset — required for Alchemy terminal operations');
  }
  return key;
}

function rpcUrl(network: AlchemyNetwork, apiKeyOverride?: string): string {
  const key = alchemyKey(apiKeyOverride);
  if (network === 'eth-mainnet') {
    return `https://eth-mainnet.g.alchemy.com/v2/${key}`;
  }
  return `https://defi-oracle-meta-mainnet.g.alchemy.com/v2/${key}`;
}

export async function alchemyJsonRpc<T = unknown>(
  network: AlchemyNetwork,
  method: string,
  params: unknown[] = [],
  apiKeyOverride?: string
): Promise<T> {
  const allowed = new Set(
    (process.env.OMNL_TERMINAL_ALCHEMY_METHODS || '')
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean)
  );
  const defaultAllowed = [
    'eth_blockNumber',
    'eth_getBalance',
    'eth_call',
    'eth_estimateGas',
    'eth_gasPrice',
    'eth_getTransactionCount',
    'eth_getTransactionReceipt',
    'eth_getTransactionByHash',
  ];
  const methods = allowed.size > 0 ? allowed : new Set(defaultAllowed);
  if (!methods.has(method)) {
    throw new Error(`Alchemy RPC method not allowed: ${method}`);
  }
  const { data } = await axios.post(
    rpcUrl(network, apiKeyOverride),
    { jsonrpc: '2.0', id: 1, method, params },
    { timeout: 30000 }
  );
  if (data.error) {
    throw new Error(data.error.message || JSON.stringify(data.error));
  }
  return data.result as T;
}

export async function alchemyTokenPricesByAddress(
  network: AlchemyNetwork,
  addresses: string[],
  apiKeyOverride?: string
): Promise<unknown> {
  const key = alchemyKey(apiKeyOverride);
  const url = `https://api.g.alchemy.com/prices/v1/${key}/tokens/by-address`;
  const { data } = await axios.post(
    url,
    {
      addresses: addresses.map((address) => ({ network, address })),
    },
    { timeout: 30000 }
  );
  return data;
}

export async function alchemyPrepareErc20Transfer(input: {
  network: AlchemyNetwork;
  from: string;
  to: string;
  tokenAddress: string;
  amountHuman: string;
  dryRun?: boolean;
  apiKeyOverride?: string;
}): Promise<{
  network: AlchemyNetwork;
  from: string;
  to: string;
  tokenAddress: string;
  amountRaw: string;
  data: string;
  gasEstimate: string | null;
  nonce: string | null;
  dryRun: boolean;
  note: string;
}> {
  const iface = new Interface(ERC20_ABI);
  const provider = rpcUrl(input.network, input.apiKeyOverride);
  const decimalsHex = await axios.post(provider, {
    jsonrpc: '2.0',
    id: 1,
    method: 'eth_call',
    params: [{ to: input.tokenAddress, data: iface.encodeFunctionData('decimals', []) }, 'latest'],
  });
  const dec = parseInt(String(decimalsHex.data?.result || '0x6'), 16);
  const amountRaw = parseUnits(input.amountHuman, dec).toString();
  const data = iface.encodeFunctionData('transfer', [input.to, amountRaw]);
  let gasEstimate: string | null = null;
  let nonce: string | null = null;
  if (!input.dryRun) {
    gasEstimate = await alchemyJsonRpc<string>(input.network, 'eth_estimateGas', [
      { from: input.from, to: input.tokenAddress, data },
    ], input.apiKeyOverride);
    nonce = await alchemyJsonRpc<string>(input.network, 'eth_getTransactionCount', [input.from, 'latest'], input.apiKeyOverride);
  }
  return {
    network: input.network,
    from: input.from,
    to: input.tokenAddress,
    tokenAddress: input.tokenAddress,
    amountRaw,
    data,
    gasEstimate,
    nonce,
    dryRun: input.dryRun !== false,
    note: 'Unsigned transfer calldata only — broadcast requires separate operator signing workflow.',
  };
}

export function alchemyConfigured(apiKeyOverride?: string): boolean {
  return Boolean(apiKeyOverride?.trim() || process.env.ALCHEMY_API_KEY?.trim());
}
