import { ethers, Contract, JsonRpcProvider, Wallet } from 'ethers';
import dotenv from "dotenv";
import express from 'express';
import { RFQsmartcontract_OIL } from './RFQsmartcontract';
import { OilPositionUserModel } from './models/oilPositionUserModel';

dotenv.config({ path: "../.env" });

// ============================================================================
// Configuration
// ============================================================================

const RPC_URL = process.env.ARB_RPC_URL || process.env.RPC_URL;
const PRIVATE_KEY = process.env.KEEPER_PRIVATE_KEY || process.env.PRIVATE_KEY;
const POLL_INTERVAL_MS = process.env.POLL_INTERVAL_MS || "60000";
const OIL_PORT = process.env.OIL_PORT || "4055";
const OIL_START_BLOCK = process.env.OIL_START_BLOCK || "0";

if (!PRIVATE_KEY) throw new Error('KEEPER_PRIVATE_KEY or PRIVATE_KEY environment variable is required');

const OIL_RFQ_CONTRACT_ADDRESS = '0x2A774195efE891cDe52c63dcf1450eD03114E22a';
const IUSD_TOKEN_ADDRESS = '0xFEF7D9E974713E56Be167C8685e253DE5ec9c8d0';

const provider = new JsonRpcProvider(RPC_URL);
const wallet = new Wallet(PRIVATE_KEY, provider);
const contract = new Contract(OIL_RFQ_CONTRACT_ADDRESS, RFQsmartcontract_OIL, wallet);

const KEEPER_ADDRESS = wallet.address;

const pollMs = Math.max(3000, Number(POLL_INTERVAL_MS));
const refreshUsersMs = Math.max(
  15000,
  Number(process.env.OIL_USERS_REFRESH_MS || "120000")
);
const inFlight = new Set<string>();
const knownUsers = new Set<string>();
let isRefreshingUsers = false;

// ============================================================================
// Helper Functions
// ============================================================================

const fmt = (value: bigint, decimals: number = 18): string => {
  return Number(ethers.formatUnits(value, decimals)).toFixed(2);
};

const fmt4 = (value: bigint): string => {
  return Number(ethers.formatUnits(value, 18)).toFixed(4);
};

const key = (owner: string, posId: number) => `${owner.toLowerCase()}:${posId}`;
const normalizeAddress = (address: string) => address.toLowerCase();

async function loadKnownUsers() {
  console.log(`\n🔎 Loading users from MySQL database...`);

  try {
    // Fetch all active oil users from database using model
    const users = await OilPositionUserModel.getAllActiveUsers();
    
    if (users.length === 0) {
      console.log(`⚠️  No users found in database`);
      console.log(`   To add users, create an Oil position in the web app`);
      console.log(`   Or use GraphQL: mutation { addOilUser(input: { wallet_address: "0x..." }) { success } }`);
      return;
    }
    
    console.log(`📊 Found ${users.length} user(s) in database`);
    
    // Verify each user and add to tracking
    for (const user of users) {
      const normalizedAddress = normalizeAddress(user.wallet_address);
      knownUsers.add(normalizedAddress);

      try {
        const nextId = await contract.nextPositionIdForUser(normalizedAddress);
        if (Number(nextId) > 0) {
          console.log(`  ✅ Tracking ${user.wallet_address} (${nextId.toString()} position(s))`);
        } else {
          console.log(`  ⚠️  ${user.wallet_address} has no positions`);
        }
      } catch (err: any) {
        console.log(`  ❌ Error checking ${user.wallet_address}: ${err.message}`);
      }
    }
    
    console.log(`✅ Loaded ${knownUsers.size} user(s) from database with active positions`);
  } catch (err: any) {
    console.error(`❌ Failed to load users from database:`, err.message);
    console.error(`   Make sure MySQL is running and the oil_position_users table exists`);
  }
}

async function refreshKnownUsersFromDb() {
  if (isRefreshingUsers) return;
  isRefreshingUsers = true;

  try {
    const users = await OilPositionUserModel.getAllActiveUsers();
    if (users.length === 0) return;

    for (const user of users) {
      const normalizedAddress = normalizeAddress(user.wallet_address);
      if (knownUsers.has(normalizedAddress)) continue;

      knownUsers.add(normalizedAddress);
      console.log(`🆕 Added new oil user from DB: ${user.wallet_address}`);
      await getAllPositionDetails(normalizedAddress);
    }
  } catch (err: any) {
    console.warn(`⚠️  Failed to refresh users from DB: ${err.message}`);
  } finally {
    isRefreshingUsers = false;
  }
}

// ============================================================================
// Position Details Fetching
// ============================================================================

async function getAllPositionDetails(owner: string) {
  console.log(`\n📊 Fetching all positions for ${owner}...`);
  
  try {
    const nextId = await contract.nextPositionIdForUser(owner) as bigint;
    const count = Number(nextId);

    if (count === 0) {
      console.log(`  No positions found for ${owner}`);
      return [];
    }

    console.log(`  Found ${count} total position(s)`);

    const positions = [];

    for (let i = 0; i < count; i++) {
      try {
        const pos = await contract.getPositionById(owner, BigInt(i));
        
        const status = pos.isActive ? '🟢 ACTIVE' : '🔴 CLOSED';
        const slPrice = pos.stopLossPrice > 0n ? fmt(pos.stopLossPrice) : 'None';
        const tpPrice = pos.takeProfitPrice > 0n ? fmt(pos.takeProfitPrice) : 'None';
        
        // Calculate total amount
        const totalAmount = pos.legs.reduce((sum: bigint, leg: any) => {
          return sum + leg.amountIWTI;
        }, 0n);

        console.log(`\n  Position #${i}:`);
        console.log(`    Status: ${status}`);
        console.log(`    ID: ${pos.positionId.toString()}`);
        console.log(`    Type: ${pos.positionType === 0 ? 'Outright' : 'Spread'}`);
        console.log(`    Total IWTI: ${fmt4(totalAmount)}`);
        console.log(`    Stop Loss: $${slPrice}`);
        console.log(`    Take Profit: $${tpPrice}`);
        console.log(`    Opened: ${new Date(Number(pos.purchaseTimestamp) * 1000).toISOString()}`);
        
        if (pos.isActive) {
          // Get current price for first leg
          try {
            const currentPrice = await contract.priceFor(pos.legs[0].maturity) as bigint;
            console.log(`    Current Price: $${fmt(currentPrice)}`);
            
            // Check SL/TP status
            const slHit = pos.stopLossPrice > 0n && currentPrice <= pos.stopLossPrice;
            const tpHit = pos.takeProfitPrice > 0n && currentPrice >= pos.takeProfitPrice;
            
            if (slHit) console.log(`    ⚠️  STOP LOSS HIT!`);
            if (tpHit) console.log(`    ✅ TAKE PROFIT HIT!`);
          } catch (err) {
            console.log(`    ⚠️  Could not fetch current price`);
          }
        }

        console.log(`    Legs:`);
        pos.legs.forEach((leg: any, idx: number) => {
          const maturityDate = new Date(Number(leg.maturity) * 1000);
          console.log(`      [${idx}] ${leg.isLong ? 'LONG' : 'SHORT'} ${fmt4(leg.amountIWTI)} IWTI @ $${fmt(leg.entryPrice)} (exp: ${maturityDate.toISOString().split('T')[0]})`);
        });

        positions.push({
          positionId: i,
          ...pos,
          totalAmount,
        });

      } catch (err: any) {
        console.error(`  ❌ Error fetching position ${i}:`, err.message);
      }
    }

    return positions;

  } catch (err: any) {
    console.error(`❌ Error fetching positions for ${owner}:`, err.message);
    return [];
  }
}

// Helper function for formatting
function formatUnits(value: bigint, decimals: number = 18): number {
  return parseFloat(ethers.formatUnits(value, decimals));
}

// ============================================================================
// Spot Price Fetching
// ============================================================================

async function getSpotPrice(): Promise<bigint | null> {
  try {
    const spotPrice = await contract.priceFor(0) as bigint; // Assuming 0 is for spot price
    return spotPrice;
  } catch (err: any) {
    console.error(`❌ Error fetching spot price:`, err.message);
    return null;
  }
}

// ============================================================================
// Auto-Close Logic - Direct Close When We Detect SL/TP Hit
// ============================================================================

async function tryClosePosition(
  owner: string,
  posId: number,
  pos: any
): Promise<boolean> {
  const key = (user: string, id: number) => `${user.toLowerCase()}_${id}`;
  const tag = key(owner, posId);
  
  if (inFlight.has(tag)) {
    return false; 
  }

  const stopLossPrice = pos.stopLossPrice as bigint;
  const takeProfitPrice = pos.takeProfitPrice as bigint;
  const hasSL = stopLossPrice > BigInt(0);
  const hasTP = takeProfitPrice > BigInt(0);

  if (!hasSL && !hasTP) {
    return false; 
  }

  const spotPrice18 = await getSpotPrice();
  if (spotPrice18 === null) {
    return false;
  }

  let currentPrice = spotPrice18;
  if (pos.legs && pos.legs.length > 0) {
    const maturity = pos.legs[0].maturity;
    try {
      const futPrice = (await contract.priceFor(maturity)) as bigint;
      currentPrice = futPrice;
    } catch {
      /* fallback to spot */
    }
  }

  const slHit = hasSL && currentPrice <= stopLossPrice;
  const tpHit = hasTP && currentPrice >= takeProfitPrice;

  if (!slHit && !tpHit) {
    return false;
  }

  const fmt = (p: bigint) => parseFloat(ethers.formatUnits(p, 18)).toFixed(2);
  const formatUnits = (wei: bigint) => parseFloat(ethers.formatUnits(wei, 18));

  let pnlWei = BigInt(0);
  if (pos.legs) {
    for (const leg of pos.legs) {
      const priceDelta = currentPrice - leg.entryPrice;
      const legPnl = (priceDelta * leg.amountIWTI) / ethers.parseUnits('1', 18);
      
      if (leg.isLong) {
        pnlWei += legPnl;
      } else {
        pnlWei -= legPnl;
      }
    }
  }

  const pnl = formatUnits(pnlWei);
  
  const reason = slHit ? 'Stop Loss' : 'Take Profit';
  console.log(`\n🎯 ${reason} HIT! Closing Position #${posId} for ${owner}`);
  console.log(`   Current: $${fmt(currentPrice)}, SL: $${hasSL ? fmt(stopLossPrice) : 'None'}, TP: $${hasTP ? fmt(takeProfitPrice) : 'None'}`);
  console.log(`   Estimated P/L: $${pnl.toFixed(2)}`);

  inFlight.add(tag);

  try {
    console.log(`   📤 Submitting close transaction...`);
    
    let tx;
    const normalizedOwner = owner.toLowerCase();
    const normalizedKeeper = KEEPER_ADDRESS.toLowerCase();
    
    // If keeper owns the position, use direct close (more reliable, no keeper auth needed)
    if (normalizedOwner === normalizedKeeper) {
      console.log(`   🔓 Using direct closePosition (owner can always close)`);
      tx = await contract.closePosition(
        BigInt(posId),
        IUSD_TOKEN_ADDRESS,
        { gasLimit: 800000 }
      );
    } else {
      // For other users' positions, use keeper function
      console.log(`   🔐 Using triggerCloseByKeeper (external owner)`);
      tx = await contract.triggerCloseByKeeper(
        owner,
        BigInt(posId),
        IUSD_TOKEN_ADDRESS,
        { gasLimit: 800000 }
      );
    }

    console.log(`   ⏳ TX submitted: ${tx.hash}`);
    const receipt = await tx.wait();
    
    if (receipt && receipt.status === 1) {
      console.log(`   ✅ Position closed successfully!`);
      console.log(`   📦 Block: ${receipt.blockNumber}`);
      console.log(`   ⛽ Gas used: ${receipt.gasUsed.toString()}`);
      console.log(`   🔗 https://arbiscan.io/tx/${tx.hash}`);
      return true;
    } else {
      console.log(`   ❌ Transaction failed (status: ${receipt?.status})`);
      return false;
    }
  } catch (err: any) {
    console.log(`   ❌ Close failed: ${err.shortMessage || err.message}`);
    
    // Try to extract and decode revert reason
    if (err.data) {
      try {
        const errorData = err.data;
        console.log(`   📋 Error data: ${errorData}`);
        
        // Check for common error selectors
        const selector = errorData.slice(0, 10);
        
        // Insufficient funds error
        if (selector === '0xe450d38c') {
          console.log(`   💡 REASON: Insufficient treasury funds for payout`);
          console.log(`   ℹ️  The RFQ treasury doesn't have enough IUSD to pay your profit`);
          console.log(`   ℹ️  Contact admin to fund the treasury`);
        } 
        // Unauthorized keeper
        else if (selector === '0x82b42900') {
          console.log(`   💡 REASON: Unauthorized - Not a keeper`);
        }
        // Position not active
        else if (selector === '0x08c379a0') {
          console.log(`   💡 REASON: Position may already be closed or not active`);
        }
        else {
          console.log(`   💡 Error selector: ${selector}`);
        }
      } catch (decodeErr) {
        console.log(`   ⚠️  Could not decode error`);
      }
    }
    
    // Check if it's an "execution reverted" error
    if (err.message && err.message.includes('execution reverted')) {
      console.log(`   💡 Transaction reverted on-chain - likely treasury funds issue`);
    }
    
    inFlight.delete(tag);
    return false;
  } finally {
    setTimeout(() => inFlight.delete(tag), 5000);
  }
}



// ============================================================================
// Main Polling Loop
// ============================================================================

async function pollOnce() {
  try {
    for (const owner of knownUsers) {
      const nextId = await contract.nextPositionIdForUser(owner) as bigint;
      const max = Number(nextId);

      if (max === 0) continue;

      for (let posId = 0; posId < max; posId++) {
        try {
          const pos = await contract.getPositionById(owner, posId);

          if (!pos.isActive) continue;

          // Try to close if SL/TP hit
          await tryClosePosition(owner, posId, pos);
          
          // Small delay to avoid rate limiting
          await new Promise(resolve => setTimeout(resolve, 2000));

        } catch (err: any) {
          console.warn(`[warn] owner=${owner} pos=${posId} error:`, err.message);
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      }
    }
  } catch (err: any) {
    console.warn(`[warn] nextPositionIdForUser failed:`, err.message);
  }
}

// ============================================================================
// Real-Time Event Listener
// ============================================================================

function listenForNewPositions() {
  console.log('👂 Listening for new positions in real-time...\n');
  
  // Listen for all PositionOpened events to track users
  contract.on('PositionOpened', async (user: string, positionId: bigint) => {
    const normalizedUser = normalizeAddress(user);
    const wasKnown = knownUsers.has(normalizedUser);
    knownUsers.add(normalizedUser);

    console.log(`\n[EVENT] 🆕 New position detected!`);
    console.log(`   Position ID: ${positionId.toString()}`);
    console.log(`   User: ${user}`);
    if (!wasKnown) console.log(`   ➕ Added new user to watchlist`);
    
    // Add user to database using model
    try {
      await OilPositionUserModel.addUser(normalizedUser);
      console.log(`   💾 User added/updated in database`);
    } catch (err: any) {
      console.warn(`   ⚠️  Could not update database: ${err.message}`);
    }
    
    console.log(`   ⚡ Checking immediately (no wait for poll)...\n`);
    
    // Immediately check this new position
    try {
      const pos = await contract.getPositionById(user, positionId);
      if (pos.isActive) {
        await tryClosePosition(normalizedUser, Number(positionId), pos);
      }
    } catch (err: any) {
      console.error(`   ❌ Error checking new position:`, err.message);
    }
  });
}

// ============================================================================
// Main Entry Point
// ============================================================================

async function main() {
  // Start Express server for health checks
  const app = express();
  
  let serviceStatus = {
    isRunning: true,
    lastCheck: new Date().toISOString(),
    activePositions: 0,
    totalChecks: 0,
    trackedUsers: 0
  };

  app.get('/health', (req, res) => {
    res.json({ status: 'ok', service: 'oil-auto-close', timestamp: new Date().toISOString() });
  });

  app.get('/status', (req, res) => {
    res.json(serviceStatus);
  });

  const server = app.listen(parseInt(OIL_PORT), () => {
    console.log(`🌐 HTTP Server running on port ${OIL_PORT}`);
  });

  console.log('🚀 Oil Auto-Close Service (Multi-User Mode)');
  console.log(`📡 RPC: ${RPC_URL}`);
  console.log(`📜 Contract: ${OIL_RFQ_CONTRACT_ADDRESS}`);
  console.log(`🧾 Keeper Wallet: ${KEEPER_ADDRESS}`);
  console.log(`⏱️  Poll Interval: ${pollMs}ms`);
  console.log(`🔓 Permission: Keeper (triggerCloseByKeeper)\n`);

  await loadKnownUsers();
  setInterval(refreshKnownUsersFromDb, refreshUsersMs);

  // Display current positions for all known users
  console.log('📋 Current Position Summary:');
  console.log('='.repeat(60));
  for (const owner of knownUsers) {
    await getAllPositionDetails(owner);
  }
  console.log('='.repeat(60));

  // Start real-time event listener
  listenForNewPositions();

  console.log('\n🤖 Auto-Close Service is now running...');
  console.log('   - ⚡ Real-time detection: NEW positions detected instantly');
  console.log('   - 🔄 Regular polling: Checks all positions every 60s');
  console.log('   - 🎯 Will auto-close when SL/TP is hit');
  console.log('Press Ctrl+C to stop\n');

  // Main polling loop (still runs for regular checks)
  while (true) {
    try {
      serviceStatus.totalChecks++;
      serviceStatus.lastCheck = new Date().toISOString();
      serviceStatus.trackedUsers = knownUsers.size;
      await pollOnce();
    } catch (err: any) {
      console.error('[poll] error:', err.message);
    }
    await new Promise(resolve => setTimeout(resolve, pollMs));
  }
}

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('\n\n👋 Shutting down...');
  process.exit(0);
});

process.on('SIGTERM', () => {
  console.log('\n\n👋 Shutting down...');
  process.exit(0);
});

// Run
main().catch((err) => {
  console.error('Fatal error:', err);
  process.exit(1);
});
