/**
 * Configuration for CCIP Relay Service
 */

import dotenv from 'dotenv';
import path from 'path';
import { existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Load project root first so PRIVATE_KEY is set, then relay .env
const projectEnv = path.resolve(__dirname, '../../../.env');
const relayEnv = path.resolve(__dirname, '../.env');
dotenv.config({ path: projectEnv });
dotenv.config({ path: relayEnv });

const DEFAULT_SOURCE_CHAIN_ID = Number(process.env.SOURCE_CHAIN_ID || '138');
const DEFAULT_DEST_CHAIN_ID = Number(process.env.DEST_CHAIN_ID || '1');
// Fill contract addresses from master JSON (config/smart-contracts-master.json) when not set in .env
const repoRoots = [
  path.resolve(__dirname, '../../../'),
  path.resolve(__dirname, '../../../../')
];

function resolveConfigFile(fileName) {
  for (const root of repoRoots) {
    const candidate = path.join(root, 'config', fileName);
    if (existsSync(candidate)) {
      return candidate;
    }
  }
  return path.join(repoRoots[0], 'config', fileName);
}

const contractsLoaderPath = resolveConfigFile('contracts-loader.cjs');
const tokenMappingLoaderPath = resolveConfigFile('token-mapping-loader.cjs');
try {
  const require = createRequire(import.meta.url);
  const { loadContractsIntoProcessEnv } = require(contractsLoaderPath);
  if (typeof loadContractsIntoProcessEnv === 'function') {
    loadContractsIntoProcessEnv([DEFAULT_SOURCE_CHAIN_ID, DEFAULT_DEST_CHAIN_ID]);
  }
} catch (_) { /* run from smom-dbis-138 only: loader not found */ }

function getExplicitTokenMappingOverrides() {
  const overrides = {};
  const destinationWeth9 =
    process.env.DEST_WETH9_ADDRESS ||
    process.env.DEST_WETH_ADDRESS ||
    '';
  const destinationLink = process.env.DEST_LINK_ADDRESS || '';

  if (destinationWeth9 && !destinationWeth9.includes('${')) {
    overrides['0xc02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase()] = destinationWeth9;
  }

  if (destinationLink && !destinationLink.includes('${')) {
    overrides['0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03'.toLowerCase()] = destinationLink;
  }

  return overrides;
}

function withExplicitTokenMappingOverrides(mapping) {
  return {
    ...(mapping || {}),
    ...getExplicitTokenMappingOverrides()
  };
}

// Token mapping for the active source/destination pair: prefer multichain mapping when available.
function getTokenMapping() {
  const sourceChainId = Number(process.env.SOURCE_CHAIN_ID || '138');
  const destinationChainId = Number(process.env.DEST_CHAIN_ID || '1');
  try {
    const require = createRequire(import.meta.url);
    const { getTokenMappingForPair, getRelayTokenMapping } = require(tokenMappingLoaderPath);
    const pair = getTokenMappingForPair && getTokenMappingForPair(sourceChainId, destinationChainId);
    if (pair && pair.addressMapFromTo && Object.keys(pair.addressMapFromTo).length > 0) {
      return withExplicitTokenMappingOverrides(pair.addressMapFromTo);
    }
    const fromFile = getRelayTokenMapping && getRelayTokenMapping();
    if (sourceChainId === 138 && destinationChainId === 1 && fromFile && Object.keys(fromFile).length > 0) {
      return withExplicitTokenMappingOverrides(fromFile);
    }
  } catch (_) { /* config not available */ }
  const destinationWeth9 = process.env.DEST_WETH9_ADDRESS || process.env.DEST_WETH_ADDRESS || '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
  // Fallback keeps WETH and LINK mapping for legacy relay profiles.
  return withExplicitTokenMappingOverrides({
    '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': destinationWeth9,
    '0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03': process.env.DEST_LINK_ADDRESS || '0x514910771AF9Ca656af840dff83E8264EcF986CA'
  });
}
// If PRIVATE_KEY still missing, try cwd-relative paths (e.g. run from repo root or relay dir)
if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY.includes('${')) {
  const cwd = process.cwd();
  const tries = [
    path.resolve(cwd, '../../.env'),        // cwd is services/relay
    path.resolve(cwd, 'smom-dbis-138/.env') // cwd is project root (proxmox)
  ];
  for (const p of tries) {
    if (p !== projectEnv && p !== relayEnv) dotenv.config({ path: p });
  }
}

function getEffectivePrivateKey() {
  const v = process.env.RELAYER_PRIVATE_KEY || process.env.PRIVATE_KEY || '';
  return (v && !v.includes('${')) ? v : (process.env.PRIVATE_KEY || '');
}

// Build mainnet RPC URL; use INFURA_PROJECT_SECRET (or METAMASK_SECRET) for Basic Auth when using Infura
function getMainnetRpcUrl() {
  let raw = process.env.RPC_URL_MAINNET || process.env.ETHEREUM_MAINNET_RPC || '';
  if (!raw && process.env.INFURA_PROJECT_ID && !String(process.env.INFURA_PROJECT_ID).includes('${')) {
    raw = `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`;
  }
  if (!raw) raw = 'https://ethereum.publicnode.com';
  const secret = process.env.INFURA_PROJECT_SECRET || process.env.METAMASK_SECRET || '';
  const infuraMatch = raw.match(/^https:\/\/mainnet\.infura\.io\/v3\/([a-f0-9]+)$/i);
  if (infuraMatch && secret && !secret.includes('${')) {
    const projectId = infuraMatch[1];
    return `https://${encodeURIComponent(projectId)}:${encodeURIComponent(secret)}@mainnet.infura.io/v3/${projectId}`;
  }
  return raw;
}

function getSourceRpcUrl() {
  return (
    process.env.SOURCE_RPC_URL ||
    process.env.RPC_URL_138_PUBLIC ||
    process.env.CHAIN138_RPC_URL_PUBLIC ||
    process.env.RPC_URL_138 ||
    process.env.RPC_URL ||
    'https://rpc.public-0138.defi-oracle.io'
  );
}

function getDestinationRelayBridgeAddress() {
  if (Object.prototype.hasOwnProperty.call(process.env, 'DEST_RELAY_BRIDGE')) {
    return process.env.DEST_RELAY_BRIDGE || '';
  }
  return (
    process.env.CCIP_RELAY_BRIDGE_MAINNET ||
    process.env.RELAY_BRIDGE_MAINNET ||
    '0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939'
  );
}

function getSkipMessageIds() {
  return new Set(
    String(process.env.RELAY_SKIP_MESSAGE_IDS || '')
      .split(',')
      .map((s) => s.trim().toLowerCase())
      .filter(Boolean)
  );
}

export const config = {
  // Source chain: defaults to Chain 138 but can be overridden for reverse relay profiles.
  sourceChain: {
    name: process.env.SOURCE_CHAIN_NAME || 'Chain 138',
    chainId: DEFAULT_SOURCE_CHAIN_ID,
    rpcUrl: getSourceRpcUrl(),
    routerAddress:
      process.env.SOURCE_ROUTER_ADDRESS ||
      process.env.SOURCE_ROUTER_ADDRESS ||
      process.env.CCIP_ROUTER_CHAIN138 ||
      process.env.CCIP_ROUTER ||
      '0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817',
    bridgeAddress:
      process.env.SOURCE_BRIDGE_ADDRESS ||
      process.env.CCIPWETH9_BRIDGE_CHAIN138 ||
      process.env.CCIPWETH9_BRIDGE_CHAIN138_LINK ||
      '0xcacfd227A040002e49e2e01626363071324f820a'
  },
  
  // Destination chain: defaults to Ethereum Mainnet for backward compatibility.
  // Override for other chains (e.g. BSC/AVAX) with DEST_* env vars.
  destinationChain: {
    name: process.env.DEST_CHAIN_NAME || 'Ethereum Mainnet',
    chainId: process.env.DEST_CHAIN_ID ? parseInt(process.env.DEST_CHAIN_ID) : 1,
    rpcUrl: process.env.DEST_RPC_URL || getMainnetRpcUrl(),
    relayRouterAddress:
      process.env.DEST_RELAY_ROUTER ||
      process.env.CCIP_RELAY_ROUTER_MAINNET ||
      process.env.RELAY_ROUTER_MAINNET ||
      '0x416564Ab73Ad5710855E98dC7bC7Bff7387285BA',
    relayBridgeAddress:
      getDestinationRelayBridgeAddress(),
    deliveryMode: process.env.DEST_DELIVERY_MODE || 'router',
    // Optional CSV allowlist for per-message receiver routing.
    // When set, relay will only forward to bridges in this list.
    relayBridgeAllowlist: (process.env.DEST_RELAY_BRIDGE_ALLOWLIST || '')
      .split(',')
      .map((s) => s.trim().toLowerCase())
      .filter(Boolean),
    chainSelector: BigInt(process.env.DEST_CHAIN_SELECTOR || '5009297550715157269'),
    weth9Address: process.env.DEST_WETH9_ADDRESS || '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
  },
  
  // Token address mapping for the active relay pair.
  tokenMapping: getTokenMapping(),
  
  // Relay profiles use explicit selectors; default source selector remains 138 for legacy profiles.
  sourceChainSelector: BigInt(process.env.SOURCE_CHAIN_SELECTOR || '138'),
  
  // Relayer configuration (dotenv does not expand ${PRIVATE_KEY}; getter uses effective key)
  relayer: {
    get privateKey() {
      return getEffectivePrivateKey();
    },
    address: process.env.RELAYER_ADDRESS || ''
  },
  
  // Monitoring configuration
  monitoring: {
    startBlock: (() => {
      const raw = process.env.START_BLOCK;
      if (raw === undefined || raw === '') return 'latest';
      const s = String(raw).trim().toLowerCase();
      if (s === 'latest') return 'latest';
      const n = parseInt(raw, 10);
      return Number.isFinite(n) ? n : 'latest';
    })(),
    pollInterval: process.env.POLL_INTERVAL ? parseInt(process.env.POLL_INTERVAL) : 5000, // 5 seconds
    confirmationBlocks: process.env.CONFIRMATION_BLOCKS ? parseInt(process.env.CONFIRMATION_BLOCKS) : 1,
    finalityDelayBlocks: process.env.FINALITY_DELAY_BLOCKS ? parseInt(process.env.FINALITY_DELAY_BLOCKS) : 2,
    replayWindowBlocks: process.env.REPLAY_WINDOW_BLOCKS ? parseInt(process.env.REPLAY_WINDOW_BLOCKS) : 32
  },
  
  // Retry configuration
  retry: {
    maxRetries: process.env.MAX_RETRIES ? parseInt(process.env.MAX_RETRIES) : 3,
    retryDelay: process.env.RETRY_DELAY ? parseInt(process.env.RETRY_DELAY) : 5000 // 5 seconds
  },

  queuePersistence: {
    path:
      process.env.RELAY_QUEUE_STATE_PATH ||
      path.resolve(__dirname, '../data/queue-state.json')
  },

  skipMessageIds: getSkipMessageIds()
};

/**
 * Relay shedding: pause **destination-chain** relay txs (saves Mainnet gas) while still polling
 * the source router so messages can queue until you turn delivery back on.
 *
 * Toggle via env (restart relay service after edits, or rely on your process manager’s reload):
 * - `RELAY_SHEDDING=1` | `true` | `yes` | `on` → shedding **on** (no `relayMessage` / direct `ccipReceive`)
 * - `RELAY_DELIVERY_ENABLED=0` | `false` | `no` | `off` → same as shedding on
 *
 * Default: shedding **off** (normal operation).
 *
 * Re-reads `process.env` on each call so a future SIGHUP/full restart pattern stays simple.
 */
export function isRelayShedding() {
  const shed = String(process.env.RELAY_SHEDDING || '').trim().toLowerCase();
  if (['1', 'true', 'yes', 'on'].includes(shed)) return true;
  const del = String(process.env.RELAY_DELIVERY_ENABLED ?? '1').trim().toLowerCase();
  if (['0', 'false', 'no', 'off'].includes(del)) return true;
  return false;
}

/** Source `eth_getLogs` poll interval while shedding (ms). Min 5000. Default 60000. */
export function getRelaySheddingSourcePollIntervalMs() {
  const v = parseInt(process.env.RELAY_SHEDDING_SOURCE_POLL_INTERVAL_MS || '60000', 10);
  return Number.isFinite(v) && v >= 5000 ? v : 60000;
}

/** Sleep between queue-processor iterations while shedding (ms). Min 1000. Default 5000. */
export function getRelaySheddingQueueIdleMs() {
  const v = parseInt(process.env.RELAY_SHEDDING_QUEUE_POLL_MS || '5000', 10);
  return Number.isFinite(v) && v >= 1000 ? v : 5000;
}

// Validate required configuration (use same helper so we validate the effective key)
if (!getEffectivePrivateKey()) {
    throw new Error('RELAYER_PRIVATE_KEY or PRIVATE_KEY environment variable is required. Set PRIVATE_KEY in smom-dbis-138/.env or RELAYER_PRIVATE_KEY in services/relay/.env');
}

// Validate relay addresses (warn but don't fail - they may be set later)
if (!config.destinationChain.relayRouterAddress) {
    console.warn('Warning: Relay router address not configured. Service will not start until configured.');
}
