import { ethers, Contract, JsonRpcProvider, Wallet } from 'ethers';
import dotenv from "dotenv";
import express from 'express';
import { RFQsmartcontract_COPPER } from './RFQsmartcontract';
import { CopperPositionUserModel } from './models/copperPositionUserModel';

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 COPPER_PORT = process.env.COPPER_PORT || "4056";

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

const COPPER_RFQ_CONTRACT_ADDRESS = '0x2364293A1c1005416337CD9fa0D58861436766b3';
const IUSD_TOKEN_ADDRESS = '0xFEF7D9E974713E56Be167C8685e253DE5ec9c8d0';

const provider = new JsonRpcProvider(RPC_URL);
const wallet = new Wallet(PRIVATE_KEY, provider);
const contract = new Contract(COPPER_RFQ_CONTRACT_ADDRESS, RFQsmartcontract_COPPER, wallet);

const KEEPER_ADDRESS = wallet.address;

const pollMs = Math.max(3000, Number(POLL_INTERVAL_MS));
const refreshUsersMs = Math.max(
  15000,
  Number(process.env.COPPER_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 {
    const users = await CopperPositionUserModel.getAllActiveUsers();
    if (users.length === 0) {
      console.log(`⚠️  No users found in database`);
      console.log(`   To add users, create a Copper position in the web app`);
      console.log(`   Or use GraphQL: mutation { addCopperUser(input: { wallet_address: "0x..." }) { success } }`);
      return;
    }
    console.log(`📊 Found ${users.length} user(s) in database`);
    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))`);
        }
      } catch (err: any) {
        console.log(`  ❌ Error checking ${user.wallet_address}: ${err.message}`);
      }
    }
    console.log(`✅ Loaded ${knownUsers.size} user(s) from database`);
  } catch (err: any) {
    console.error(`❌ Failed to load users:`, err.message);
  }
}

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

  try {
    const users = await CopperPositionUserModel.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 copper 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.amountICU;
        }, 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 ICU: ${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.amountICU)} ICU @ $${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 [];
  }
}

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

async function tryClosePosition(owner: string, posId: number, pos: any) {
  const stopLossPrice = pos.stopLossPrice;
  const takeProfitPrice = pos.takeProfitPrice;
  
  const hasSL = stopLossPrice > 0n;
  const hasTP = takeProfitPrice > 0n;

  if (!hasSL && !hasTP) {
    return false; // No SL/TP set, skip
  }

  // Get current price to check if SL/TP hit
  try {
    const currentPrice = await contract.priceFor(pos.legs[0].maturity) as bigint;
    
    const slHit = hasSL && currentPrice <= stopLossPrice;
    const tpHit = hasTP && currentPrice >= takeProfitPrice;

    if (!slHit && !tpHit) {
      // Neither SL nor TP hit yet
      return false;
    }

    // Calculate P/L to determine if we need to send ETH
    let pnlWei = 0n;
    for (const leg of pos.legs) {
      const priceDelta = currentPrice - leg.entryPrice;
      const legPnl = (priceDelta * leg.amountICU) / ethers.parseUnits('1', 18);
      
      if (leg.isLong) {
        pnlWei += legPnl;
      } else {
        pnlWei -= legPnl;
      }
    }

    const pnl = formatUnits(pnlWei);
    
    // SL or TP hit! Close directly using owner function
    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)}`);

    const tag = key(owner, posId);
    if (inFlight.has(tag)) {
      console.log(`   ⏭️  Already processing, skipping`);
      return false;
    }

    inFlight.add(tag);

    try {
      console.log(`   📤 Submitting close transaction...`);
      
      let tx;
      const normalizedOwner = normalizeAddress(owner);
      const normalizedKeeper = normalizeAddress(KEEPER_ADDRESS);
      
      if (normalizedOwner === normalizedKeeper) {
        console.log(`   � Using closePosition (owner)`);
        tx = await contract.closePosition(
          BigInt(posId),
          IUSD_TOKEN_ADDRESS,
          { gasLimit: 800000 }
        );
      } else {
        console.log(`   � Using triggerCloseByKeeper (external)`);
        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}`);
      inFlight.delete(tag);
      return false;
    } finally {
      setTimeout(() => inFlight.delete(tag), 5000);
    }

  } catch (err: any) {
    console.error(`   ⚠️  Error checking price:`, err.message);
    return false;
  }
}

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

// ============================================================================
// 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;
          await tryClosePosition(owner, posId, pos);
          await new Promise(resolve => setTimeout(resolve, 2000));
        } catch (err: any) {
          console.warn(`[warn] ${owner} pos=${posId}:`, err.message);
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      }
    }
  } catch (err: any) {
    console.warn(`[warn] polling error:`, err.message);
  }
}

function listenForNewPositions() {
  console.log('👂 Listening for new positions...\n');
  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: ${user}, ID ${positionId}`);
    if (!wasKnown) {
      console.log(`   📝 New user! Adding to database...`);
      try {
        await CopperPositionUserModel.addUser(normalizedUser);
        console.log(`   ✅ Added to database`);
      } catch (err: any) {
        console.warn(`   ⚠️  DB error: ${err.message}`);
      }
    }
    console.log(`   ⚡ Checking...`);
    try {
      const pos = await contract.getPositionById(user, positionId);
      if (pos.isActive) await tryClosePosition(user, Number(positionId), pos);
    } catch (err: any) {
      console.error(`   ❌ Error:`, 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
  };

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

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

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

  console.log('🚀 Copper Auto-Close Service (Multi-User Mode)');
  console.log(`📡 RPC: ${RPC_URL}`);
  console.log(`📜 Contract: ${COPPER_RFQ_CONTRACT_ADDRESS}`);
  console.log(`� Keeper Wallet: ${KEEPER_ADDRESS}`);
  console.log(`⏱️  Poll Interval: ${pollMs}ms`);
  console.log(`🔓 Permission: Dual-mode ...`);

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

  if (knownUsers.size > 0) {
    console.log('\n📋 Current Positions:');
    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();
      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);
});
