const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { ethers } = require("ethers");
require("dotenv").config();

// Simple relayer without cluster for testing
const SUPPORTED_NETWORKS = {
  somnia: {
    name: "Somnia Testnet",
    chainId: 50312,
    rpcUrl: "https://dream-rpc.somnia.network/",
    explorer: "https://explorer.somnia.network/",
    contract: "0xdbA1cd8558E036a957e1Ba9040BCaA78A4825221"
  },
  monad: {
    name: "Monad Testnet",
    chainId: 10143,
    rpcUrl: "https://testnet-rpc.monad.xyz",
    explorer: "https://monad-testnet.socialscan.io/",
    contract: "0x0238C45aDE925d7822B94FFf6D35a9C414532996"
  },
  soneium: {
    name: "Soneium Testnet",
    chainId: 1868,
    rpcUrl: "https://rpc.soneium.org",
    explorer: "https://soneium.blockscout.com/",
    contract: "0xf030c7ca1d49e59a9b1ddf835c4411acf7c0e858" 
  },
  megaETH: {
    name: "MegaETH Testnet",
    chainId: 6342, // Update if necessary
    rpcUrl: "https://carrot.megaeth.com/rpc", // Update if necessary
    explorer: "https://web3.okx.com/ro/explorer/megaeth-testnet",
    contract: "0xdf2c4b9ab19ed260858936adb5bf25a6c32d8e30"
    //contract: "0xaf70023547afdcf28baecee37a23aa49eed69df4"
  }
};

// NFT contract ABI
const gaslessNFTAbi = [
  {
    inputs: [
      { internalType: "address", name: "user", type: "address" },
      { internalType: "uint256", name: "tokenId", type: "uint256" },
      { internalType: "bytes32", name: "sigR", type: "bytes32" },
      { internalType: "bytes32", name: "sigS", type: "bytes32" },
      { internalType: "uint8", name: "sigV", type: "uint8" },
    ],
    name: "mintNFT",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [{ internalType: "address", name: "user", type: "address" }],
    name: "hasMinted",
    outputs: [{ internalType: "bool", name: "", type: "bool" }],
    stateMutability: "view",
    type: "function",
  },
];

// Initialize providers, wallets, contracts, transactions
const providers = {};
const wallets = {};
const contracts = {};
const transactions = {};

// Get chain-specific private keys
const PRIVATE_KEYS = {
  somnia: process.env.RELAYER_PRIVATE_KEY_SOMNIA,
  monad: process.env.RELAYER_PRIVATE_KEY_MONAD,
  soneium: process.env.RELAYER_PRIVATE_KEY_SONEIUM,
  megaETH: process.env.RELAYER_PRIVATE_KEY_MEGAETH,
  // Fallback to the generic key if a chain-specific one isn't provided
  default: process.env.RELAYER_PRIVATE_KEY
};

// Validate that we have at least some private keys
if (!PRIVATE_KEYS.default && 
    !PRIVATE_KEYS.somnia && 
    !PRIVATE_KEYS.monad && 
    !PRIVATE_KEYS.soneium) {
  console.error("No relayer private keys found in .env file");
  process.exit(1);
}

// Get the appropriate private key for a chain
function getPrivateKeyForChain(chain) {
  return PRIVATE_KEYS[chain] || PRIVATE_KEYS.default;
}

// Initialize a single network with timeout
async function initializeNetwork(chain, config) {
  console.log(`Starting initialization for ${config.name}...`);
  
  try {
    // Get the chain-specific private key
    const privateKey = getPrivateKeyForChain(chain);
    
    // Skip this chain if no private key is available
    if (!privateKey) {
      console.warn(`⚠️ Skipping ${config.name} - No private key provided`);
      return false;
    }
    
    // Create provider with timeout
    const providerPromise = new Promise((resolve, reject) => {
      try {
        const provider = new ethers.JsonRpcProvider(config.rpcUrl);
        resolve(provider);
      } catch (err) {
        reject(err);
      }
    });
    
    // Add timeout for provider creation
    providers[chain] = await Promise.race([
      providerPromise,
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error(`Provider creation timeout for ${config.name}`)), 15000)
      )
    ]);
    
    console.log(`✓ ${config.name} provider connected`);
    
    // Create wallet with the chain-specific private key
    wallets[chain] = new ethers.Wallet(privateKey, providers[chain]);
    console.log(`✓ ${config.name} wallet created: ${wallets[chain].address}`);
    
    // Create contract instance
    contracts[chain] = new ethers.Contract(
      config.contract, 
      gaslessNFTAbi, 
      wallets[chain]
    );
    console.log(`✓ ${config.name} contract instance created`);
    
    // Try to get current nonce with timeout
    try {
      console.log(`Getting nonce for ${config.name}...`);
      const noncePromise = wallets[chain].getNonce();
      
      const nonce = await Promise.race([
        noncePromise,
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error(`Nonce retrieval timeout for ${config.name}`)), 10000)
        )
      ]);
      
      console.log(`✅ ${config.name} initialization complete - wallet: ${wallets[chain].address} (nonce: ${nonce})`);
      return true;
    } catch (nonceError) {
      console.error(`⚠️ Error getting nonce for ${config.name}:`, nonceError.message);
      console.log(`✅ ${config.name} initialization partially complete - proceeding without nonce verification`);
      return true; // Still consider it initialized even if nonce check fails
    }
  } catch (error) {
    console.error(`❌ Error initializing ${config.name}:`, error.message);
    return false;
  }
}

// Initialize all networks
async function initializeNetworks() {
  const results = {};
  
  // Initialize networks in parallel with individual timeouts
  const initPromises = Object.entries(SUPPORTED_NETWORKS).map(
    async ([chain, config]) => {
      try {
        results[chain] = await initializeNetwork(chain, config);
      } catch (err) {
        console.error(`Failed to initialize ${config.name}:`, err);
        results[chain] = false;
      }
    }
  );
  
  // Wait for all networks to complete initialization attempts
  await Promise.all(initPromises);
  
  return results;
}

async function checkHasMinted(chain, address) {
  try {
    if (!contracts[chain]) {
      throw new Error(`No contract initialized for chain: ${chain}`);
    }
    
    // Add timeout for contract calls
    const hasMintedPromise = contracts[chain].hasMinted(address);
    return await Promise.race([
      hasMintedPromise,
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error(`hasMinted check timeout for ${chain}`)), 15000)
      )
    ]);
  } catch (error) {
    console.warn(`Error checking if ${address} has minted on ${chain}:`, error.message);
    return false;
  }
}

async function processMintRequest(chain, user, tokenId, sigR, sigS, sigV, requestId) {
  // Use the passed requestId instead of creating a new one
  transactions[requestId] = {
    chain,
    user,
    tokenId,
    status: 'processing',
    startTime: Date.now()
  };
  
  console.log(`Created transaction ${requestId} with status 'processing'`);
  
  try {
    // Check if chain is supported
    if (!contracts[chain]) {
      throw new Error(`Chain ${chain} is not supported or no active relayer`);
    }
    
    // Check if already minted
    const hasMinted = await checkHasMinted(chain, user);
    if (hasMinted) {
      transactions[requestId].status = 'already_minted';
      transactions[requestId].error = 'User has already minted an NFT on this chain';
      console.log(`Updated transaction ${requestId} status to 'already_minted'`);
      return {
        success: false,
        status: 'already_minted',
        error: 'User has already minted an NFT on this chain',
        requestId
      };
    }
    
    // Submit transaction with timeout
    console.log(`Submitting transaction on ${chain} for user ${user} with token ID ${tokenId}...`);
    
    const mintPromise = contracts[chain].mintNFT(
      user,
      tokenId,
      sigR,
      sigS,
      sigV,
      {
        gasLimit: 500000
      }
    );
    
    const tx = await Promise.race([
      mintPromise,
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error(`Transaction submission timeout for ${chain}`)), 30000)
      )
    ]);
    
    transactions[requestId].txHash = tx.hash;
    transactions[requestId].status = 'submitted';
    
    console.log(`Transaction submitted on ${chain}: ${tx.hash}`);
    console.log(`Updated transaction ${requestId} status to 'submitted'`);
    
    // Wait for transaction confirmation with timeout
    const confirmationPromise = tx.wait(1);
    const receipt = await Promise.race([
      confirmationPromise,
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error(`Transaction confirmation timeout for ${chain}`)), 120000)
      )
    ]);
    
    transactions[requestId].status = 'confirmed';
    transactions[requestId].blockNumber = receipt.blockNumber;
    transactions[requestId].blockHash = receipt.blockHash;
    
    console.log(`Transaction confirmed on ${chain}: ${tx.hash}`);
    console.log(`Updated transaction ${requestId} status to 'confirmed'`);
    
    return {
      success: true,
      status: 'confirmed',
      txHash: tx.hash,
      blockNumber: receipt.blockNumber,
      requestId
    };
  } catch (error) {
    console.error(`Error processing mint request on ${chain}:`, error);
    
    transactions[requestId].status = 'failed';
    transactions[requestId].error = error.message;
    console.log(`Updated transaction ${requestId} status to 'failed'`);
    
    return {
      success: false,
      status: 'failed',
      error: error.message,
      requestId
    };
  }
}

// Setup Express server
const app = express();
app.use(cors());
app.use(bodyParser.json({ limit: '5mb' }));
app.use(express.json({ limit: '5mb' }));

// Add request logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

// Health check endpoint
app.get("/health", (req, res) => {
  const activeChains = Object.keys(wallets);
  const inactiveChains = Object.keys(SUPPORTED_NETWORKS).filter(chain => !wallets[chain]);
  
  res.json({
    status: "ok",
    timestamp: new Date().toISOString(),
    supportedNetworks: Object.keys(SUPPORTED_NETWORKS),
    activeRelayers: activeChains,
    inactiveNetworks: inactiveChains,
    relayerAddresses: Object.fromEntries(
      activeChains.map(chain => [chain, wallets[chain].address])
    ),
    activeTransactions: Object.keys(transactions).length
  });
});


// Status check endpoint (updated route to include chain)
app.get("/status/:chain/:requestId", async (req, res) => {
  const { chain, requestId } = req.params;
  console.log(`Status request for ${chain}/${requestId}`);
  
  if (!SUPPORTED_NETWORKS[chain]) {
    console.log(`Invalid chain: ${chain}`);
    return res.status(400).json({
      success: false,
      error: `Unsupported chain: ${chain}`
    });
  }
  
  if (transactions[requestId]) {
    console.log(`Returning status for ${requestId}: ${transactions[requestId].status}`);
    return res.json({
      success: true,
      ...transactions[requestId]
    });
  }
  
  console.log(`Transaction not found: ${requestId}`);
  return res.status(404).json({
    success: false,
    error: "Transaction not found"
  });
});

// Minting endpoint
app.post("/relay", async (req, res) => {
  const { chain, user, tokenId, sigR, sigS, sigV } = req.body;
  
  // Input validation
  if (!chain || !SUPPORTED_NETWORKS[chain]) {
    return res.status(400).json({
      success: false,
      error: "Invalid or missing chain parameter",
      supportedChains: Object.keys(SUPPORTED_NETWORKS)
    });
  }
  
  // Check if we have an active relayer for this chain
  if (!wallets[chain]) {
    return res.status(503).json({
      success: false,
      error: `No active relayer available for ${chain}`,
      activeRelayers: Object.keys(wallets)
    });
  }
  
  if (!user || tokenId === undefined || !sigR || !sigS || sigV === undefined) {
    return res.status(400).json({
      success: false,
      error: "Missing required parameters"
    });
  }
  
  try {
    // Generate a single request ID for the transaction
    const requestId = `req_${chain}_${Date.now()}_${Math.floor(Math.random() * 1000000)}`;
    res.status(202).json({
      success: true,
      message: "Transaction submitted for processing",
      requestId,
      chain
    });
    // Process the request asynchronously and pass the requestId
    processMintRequest(chain, user, tokenId, sigR, sigS, sigV, requestId)
      .then(result => {
        console.log(`Request ${requestId} completed with status: ${result.status}`);
      })
      .catch(error => {
        console.error(`Request ${requestId} failed:`, error);
      });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// Check mint status
app.get("/hasMinted/:chain/:address", async (req, res) => {
  try {
    const { chain, address } = req.params;
    
    if (!SUPPORTED_NETWORKS[chain]) {
      return res.status(400).json({
        success: false,
        error: `Unsupported chain: ${chain}`
      });
    }
    
    // Check if we have an active relayer for this chain
    if (!contracts[chain]) {
      return res.status(503).json({
        success: false,
        error: `No active relayer available for ${chain}`,
        activeRelayers: Object.keys(wallets)
      });
    }
    
    if (!ethers.isAddress(address)) {
      return res.status(400).json({
        success: false,
        error: "Invalid Ethereum address format"
      });
    }
    
    const hasMinted = await checkHasMinted(chain, address);
    
    return res.json({
      success: true,
      hasMinted,
      chain
    });
  } catch (error) {
    return res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// List transactions endpoint
app.get("/transactions/:chain", (req, res) => {
  const { chain } = req.params;
  
  if (!SUPPORTED_NETWORKS[chain]) {
    return res.status(400).json({
      success: false,
      error: `Unsupported chain: ${chain}`
    });
  }
  
  // Filter transactions by chain
  const chainTransactions = Object.entries(transactions)
    .filter(([_, tx]) => tx.chain === chain)
    .map(([id, tx]) => ({
      requestId: id,
      ...tx
    }));
  
  return res.json({
    success: true,
    transactions: chainTransactions,
    count: chainTransactions.length
  });
});

// Error handling middleware
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ 
    success: false,
    error: 'Internal server error',
    message: err.message
  });
});

// Start the server
const PORT = process.env.PORT || 3000;

// Main function to start everything
async function startServer() {
  console.log("Initializing network connections...");
  
  try {
    // Initialize all networks with improved error handling
    const initResults = await initializeNetworks();
    
    const activeChains = Object.keys(wallets);
    const successCount = Object.values(initResults).filter(Boolean).length;
    const totalCount = Object.keys(initResults).length;
    
    console.log(`\n📊 Initialization summary: ${successCount}/${totalCount} networks activated`);
    
    if (activeChains.length === 0) {
      console.error("❌ No networks could be initialized! Check your RPC URLs and private keys.");
      process.exit(1);
    }
    
    app.listen(PORT, () => {
      console.log(`\n🚀 Multi-Chain Relayer running on http://localhost:${PORT}`);
      console.log(`📋 Supported chains: ${Object.keys(SUPPORTED_NETWORKS).join(', ')}`);
      console.log(`✅ Active relayers: ${activeChains.join(', ')}`);
      
      // Display relayer addresses for active chains
      console.log("\n🔑 Relayer Addresses:");
      for (const chain of activeChains) {
        console.log(`   ${SUPPORTED_NETWORKS[chain].name}: ${wallets[chain].address}`);
      }
      
      console.log("\n📡 API Endpoints:");
      console.log(`   GET  /health                       - Check relayer status`);
      console.log(`   GET  /hasMinted/:chain/:address    - Check if address has minted on chain`);
      console.log(`   GET  /status/:chain/:requestId     - Check transaction status`);
      console.log(`   GET  /transactions/:chain          - List transactions for a chain`);
      console.log(`   POST /relay                        - Submit a new transaction`);
    });
  } catch (error) {
    console.error("❌ Failed to start relayer service:", error);
    process.exit(1);
  }
}

// Start the server
startServer().catch(error => {
  console.error("Fatal error:", error);
  process.exit(1);
});
