const { spawn } = require('child_process');
const { PrismaClient } = require('@prisma/client');
const { PrismaPg } = require('@prisma/adapter-pg');
const { Pool } = require('pg');
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
require('dotenv').config();

const PORT = 3015;
const BASE_URL = `http://localhost:${PORT}`;
const SCREENSHOT_DIR = path.join(__dirname, 'screenshots-phase5');

let nextProcess;
let prisma;
let pool;
let browser;

// Ensure screenshot dir exists
if (!fs.existsSync(SCREENSHOT_DIR)) {
  fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}

function getPrisma() {
  if (!prisma) {
    const dbUrl = new URL(process.env.DATABASE_URL);
    pool = new Pool({
      host: dbUrl.hostname,
      port: dbUrl.port ? parseInt(dbUrl.port) : 5432,
      user: decodeURIComponent(dbUrl.username),
      password: decodeURIComponent(dbUrl.password),
      database: dbUrl.pathname.substring(1).split('?')[0],
      ssl: false
    });
    const adapter = new PrismaPg(pool);
    prisma = new PrismaClient({ adapter });
  }
  return prisma;
}

async function runAsAdmin(queryFn) {
  const db = getPrisma();
  return db.$transaction(async (tx) => {
    await tx.$executeRawUnsafe(`SET LOCAL app.current_user_role = 'admin';`);
    return queryFn(tx);
  });
}

async function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

let passed = 0;
let failed = 0;

function assert(condition, message) {
  if (condition) {
    console.log(`  ✓ PASS: ${message}`);
    passed++;
  } else {
    console.error(`  ✗ FAIL: ${message}`);
    failed++;
  }
}

async function cleanupDb() {
  console.log("Cleaning up database records for Phase 5 tests...");
  await runAsAdmin(async (tx) => {
    // Delete settlements created during E2E tests
    await tx.settlement.deleteMany({
      where: {
        OR: [
          { period: '2026-06' },
          { period: '2026-05' }
        ]
      }
    });

    // Delete goals created during E2E tests
    await tx.goal.deleteMany({
      where: {
        OR: [
          { period: '2026-06' },
          { period: '2026-05' }
        ]
      }
    });

    // Delete sales results created during E2E tests
    await tx.salesResult.deleteMany({
      where: {
        OR: [
          { period: '2026-06' },
          { period: '2026-05' }
        ]
      }
    });

    // Delete test plans
    await tx.calculationRule.deleteMany({
      where: { plan: { code: 'PLAN-E2E-P5' } }
    });
    await tx.compensationPlan.deleteMany({
      where: { code: 'PLAN-E2E-P5' }
    });
  });
}

async function runTests() {
  console.log("=== STARTING PHASE 5 SETTLEMENT ENGINE & APPROVALS E2E PUPPETEER TEST SUITE ===");

  // 1. Clean database
  await cleanupDb();

  // 2. Seed active plan & rules for the tests
  console.log("Seeding test plan and rules...");
  const users = await runAsAdmin(tx => tx.user.findMany());
  const adminUser = users.find(u => u.username === 'admin');
  const colaboradorMde = users.find(u => u.username === 'colaborador_mde');
  const liderCtg = users.find(u => u.username === 'lider_ctg');

  if (!colaboradorMde || !liderCtg) {
    console.error("Error: Collaborator or Leader user not found in database.");
    process.exit(1);
  }

  const testPlan = await runAsAdmin(async (tx) => {
    const plan = await tx.compensationPlan.create({
      data: {
        name: 'Plan Test Phase 5',
        code: 'PLAN-E2E-P5',
        validityStart: new Date('2026-01-01'),
        type: 'SCALE',
        status: 'ACTIVE',
        version: 1,
        createdBy: adminUser.id
      }
    });

    await tx.calculationRule.createMany({
      data: [
        {
          planId: plan.id,
          type: 'TIER',
          minAchievement: 0.0,
          maxAchievement: 0.8,
          rate: 0.01,
          payoutAmount: 0.0
        },
        {
          planId: plan.id,
          type: 'TIER',
          minAchievement: 0.8,
          maxAchievement: 1.2,
          rate: 0.02,
          payoutAmount: 0.0
        },
        {
          planId: plan.id,
          type: 'BONUS',
          minAchievement: 1.0,
          maxAchievement: 2.0,
          rate: 0.0,
          payoutAmount: 500.00
        }
      ]
    });

    return plan;
  });

  // 3. Start Next.js production server on port 3015
  console.log(`Starting Next.js production server on port ${PORT}...`);
  nextProcess = spawn('node', ['node_modules/next/dist/bin/next', 'start', '--port', String(PORT)], {
    env: { ...process.env, PORT: String(PORT), IS_E2E_TEST: 'true' }
  });

  nextProcess.stdout.on('data', (data) => {
    console.log(`[Next.js] ${data.toString().trim()}`);
  });
  nextProcess.stderr.on('data', (data) => {
    console.error(`[Next.js ERR] ${data.toString().trim()}`);
  });

  // Wait for server to start up
  let serverReady = false;
  for (let i = 0; i < 15; i++) {
    await sleep(2000);
    try {
      const res = await fetch(`${BASE_URL}/api/auth/me`);
      serverReady = true;
      break;
    } catch (e) {
      // Server not ready yet
    }
  }

  if (!serverReady) {
    console.error("Error: Next.js server failed to start within timeout.");
    await cleanup();
    process.exit(1);
  }
  console.log("Next.js server is ready!");

  // 4. Launch Puppeteer browser
  console.log("Launching Puppeteer browser...");
  browser = await puppeteer.launch({
    headless: 'shell',
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
  });

  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });
  await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);

  // Helper to set value of React-controlled inputs natively
  const setReactInput = async (selector, value) => {
    await page.$eval(selector, (el, val) => {
      const nativeSetter = Object.getOwnPropertyDescriptor(
        HTMLInputElement.prototype,
        "value"
      ).set;
      nativeSetter.call(el, val);
      el.dispatchEvent(new Event('input', { bubbles: true }));
      el.dispatchEvent(new Event('change', { bubbles: true }));
    }, value);
  };

  // --- TEST 1: ENGINE ACCURACY ---
  console.log("\n[Test 1] Seeding sales/goals and running calculation...");
  
  // Seed goal and sales results for 2026-06
  await runAsAdmin(async (tx) => {
    await tx.goal.create({
      data: {
        targetType: 'INDIVIDUAL',
        targetId: colaboradorMde.id,
        period: '2026-06',
        amount: 10000.00
      }
    });

    await tx.salesResult.create({
      data: {
        source: 'EXCEL',
        hotelId: colaboradorMde.hotelId,
        userId: colaboradorMde.id,
        period: '2026-06',
        amount: 9000.00,
        salesCount: 5,
        idempotencyKey: 'key-p5-test-1',
        uploadedBy: adminUser.id
      }
    });
  });

  // Login as admin
  await page.goto(`${BASE_URL}/login`, { waitUntil: 'networkidle2' });
  await page.type('#username', 'admin');
  await page.type('#password', 'password123');
  await page.click('button[type="submit"]');
  await page.waitForSelector('#nav-simulation');

  // Go to simulation page
  await page.click('#nav-simulation');
  await page.waitForSelector('#sim-period');

  // Run simulation in commit mode (check off dry-run)
  await setReactInput('#sim-period', '2026-06');
  
  // Uncheck dry-run checkbox to write to DB
  const isChecked1 = await page.$eval('#sim-dry-run', el => el.checked);
  if (isChecked1) {
    await page.click('#sim-dry-run');
  }

  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '01_simulation_ready.png') });
  await page.click('#btn-run-simulation');
  await page.waitForSelector('#simulation-success-msg');
  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '02_simulation_success.png') });

  // Verify in database
  const dbSettlement = await runAsAdmin(tx => tx.settlement.findFirst({
    where: {
      userId: colaboradorMde.id,
      period: '2026-06',
      originalSettlementId: null
    }
  }));

  assert(dbSettlement !== null, "Settlement record created in database for colaborador_mde in 2026-06");
  assert(dbSettlement.status === 'PENDING', "Settlement status is set to PENDING");
  assert(parseFloat(dbSettlement.salesAmount) === 9000.00, "Sales amount is correctly 9,000.00");
  assert(parseFloat(dbSettlement.calculatedCommission) === 180.00, "Proposed commission is 180.00 (9,000 * 2%)");
  assert(parseFloat(dbSettlement.totalPayout) === 180.00, "Total payout is 180.00");

  // --- TEST 2: LEADER ISOLATION & REJECTION CONSTRAINT ---
  console.log("\n[Test 2] Verifying leader isolation and rejection constraint...");
  
  // Logout admin
  await page.goto(`${BASE_URL}/plans`, { waitUntil: 'networkidle2' });
  await page.evaluate(() => {
    const buttons = Array.from(document.querySelectorAll('button'));
    const logoutBtn = buttons.find(b => b.textContent.includes('Cerrar Sesión'));
    if (logoutBtn) logoutBtn.click();
  });
  await page.waitForSelector('#username');

  // Login as lider_ctg (Caribe region leader)
  await page.goto(`${BASE_URL}/login`, { waitUntil: 'networkidle2' });
  await page.type('#username', 'lider_ctg');
  await page.type('#password', 'password123');
  await page.click('button[type="submit"]');
  await page.waitForSelector('#nav-approvals');

  // Navigate to Approvals
  await page.click('#nav-approvals');
  await page.waitForSelector('#approvals-table');
  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '03_lider_ctg_approvals_view.png') });

  // Leader of Caribe should not see colaborador_mde's settlement in the table (which is in Antioquia region)
  const hasColabRow = await page.evaluate((username) => {
    return document.body.innerHTML.includes(username);
  }, colaboradorMde.username);
  assert(!hasColabRow, "Leader of Caribe region cannot see collaborator of Antioquia region in the segregated list");

  const directApproveRes = await page.evaluate(async (settlementId) => {
    const res = await fetch(`/api/settlements/${settlementId}/approve`, {
      method: 'POST'
    });
    return { status: res.status };
  }, dbSettlement.id);
  assert(directApproveRes.status === 403, "Direct API call to approve another region's settlement returns 403 Forbidden, got " + directApproveRes.status);

  const directRejectRes = await page.evaluate(async (settlementId) => {
    const res = await fetch(`/api/settlements/${settlementId}/reject`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ reason: 'Malicioso' })
    });
    return { status: res.status };
  }, dbSettlement.id);
  assert(directRejectRes.status === 403, "Direct API call to reject another region's settlement returns 403 Forbidden, got " + directRejectRes.status);

  // Logout lider_ctg
  await page.goto(`${BASE_URL}/plans`, { waitUntil: 'networkidle2' });
  await page.evaluate(() => {
    const buttons = Array.from(document.querySelectorAll('button'));
    const logoutBtn = buttons.find(b => b.textContent.includes('Cerrar Sesión'));
    if (logoutBtn) logoutBtn.click();
  });
  await page.waitForSelector('#username');

  // Login as admin to test rejection constraint
  await page.goto(`${BASE_URL}/login`, { waitUntil: 'networkidle2' });
  await page.type('#username', 'admin');
  await page.type('#password', 'password123');
  await page.click('button[type="submit"]');
  await page.waitForSelector('#nav-approvals');

  // Navigate to Approvals
  await page.click('#nav-approvals');
  await page.waitForSelector('#approvals-table');

  // Verify direct reject call with empty reason returns 400
  const emptyReasonRes = await page.evaluate(async (settlementId) => {
    const res = await fetch(`/api/settlements/${settlementId}/reject`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ reason: '' })
    });
    return { status: res.status };
  }, dbSettlement.id);
  assert(emptyReasonRes.status === 400, "Direct API call to reject settlement with empty reason returns 400 Bad Request");

  // Reject the settlement using UI Modal
  await page.click(`#btn-reject-${dbSettlement.id}`);
  await page.waitForSelector('#reject-reason-input');
  await page.type('#reject-reason-input', 'Venta reportada del colaborador no coincide con el cierre del hotel');
  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '04_rejection_modal.png') });
  await page.click('#btn-confirm-reject');
  await page.waitForSelector('#approvals-success-msg');
  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '05_rejection_done.png') });

  // Verify status in DB
  const rejectedSettlement = await runAsAdmin(tx => tx.settlement.findUnique({
    where: { id: dbSettlement.id }
  }));
  assert(rejectedSettlement.status === 'REJECTED', "Settlement status updated to REJECTED in database");
  assert(rejectedSettlement.rejectionReason === 'Venta reportada del colaborador no coincide con el cierre del hotel', "Rejection reason successfully stored in database");

  // --- TEST 3: CLAWBACK DELTA LOGIC ---
  console.log("\n[Test 3] Testing clawback delta adjustments...");

  // Reset database state:
  // Let's create an approved settlement for Month M-1 (2026-05) and simulate refund
  await runAsAdmin(async (tx) => {
    // Delete any existing settlement for 2026-05
    await tx.settlement.deleteMany({
      where: { userId: colaboradorMde.id, period: '2026-05' }
    });

    // Create Goal for 2026-05
    await tx.goal.create({
      data: {
        targetType: 'INDIVIDUAL',
        targetId: colaboradorMde.id,
        period: '2026-05',
        amount: 10000.00
      }
    });

    // Create SalesResult for 2026-05 (Original sales amount = 10,000.00)
    await tx.salesResult.create({
      data: {
        source: 'EXCEL',
        hotelId: colaboradorMde.hotelId,
        userId: colaboradorMde.id,
        period: '2026-05',
        amount: 10000.00,
        salesCount: 5,
        idempotencyKey: 'key-p5-past-sales',
        uploadedBy: adminUser.id
      }
    });

    // Create APPROVED original settlement for 2026-05
    // Payout = 10,000 * 2% + 500 bonus = 700.00
    await tx.settlement.create({
      data: {
        period: '2026-05',
        planId: testPlan.id,
        userId: colaboradorMde.id,
        salesAmount: 10000.00,
        goalAmount: 10000.00,
        achievementPercentage: 1.00,
        calculatedCommission: 200.00,
        calculatedBonus: 500.00,
        adjustmentAmount: 0.00,
        totalPayout: 700.00,
        status: 'APPROVED',
        approvedBy: adminUser.id,
        approvedAt: new Date()
      }
    });

    // Now, simulate a cancellation/refund in the PMS:
    // Update the SalesResult for 2026-05 to be 5,000.00 instead of 10,000.00
    await tx.salesResult.update({
      where: { idempotencyKey: 'key-p5-past-sales' },
      data: { amount: 5000.00 }
    });
  });

  // Calculate 2026-06 settlements again.
  // Current month (2026-06) sales = 9,000.00, Goal = 10,000.00.
  // Recalculating 2026-05:
  // - Live sales sum = 5,000.00
  // - Achievement = 50%
  // - Corrected payout = 5,000 * 1% = 50.00 (No bonus since achievement < 1.0)
  // - Previously paid = 700.00
  // - Delta = 50.00 - 700.00 = -650.00
  // Current month proposed = 9,000 * 2% = 180.00
  // Total payout = 180.00 + (-650.00) = -470.00

  // Navigate to simulation page again
  await page.goto(`${BASE_URL}/sales/simulation`, { waitUntil: 'networkidle2' });
  await setReactInput('#sim-period', '2026-06');
  
  // Uncheck dry-run checkbox to write to DB
  const isChecked2 = await page.$eval('#sim-dry-run', el => el.checked);
  if (isChecked2) {
    await page.click('#sim-dry-run');
  }

  await page.click('#btn-run-simulation');
  await page.waitForSelector('#simulation-success-msg');
  await page.screenshot({ path: path.join(SCREENSHOT_DIR, '06_clawback_calculated.png') });

  // Verify DB record
  const finalSettlement = await runAsAdmin(tx => tx.settlement.findFirst({
    where: {
      userId: colaboradorMde.id,
      period: '2026-06',
      originalSettlementId: null
    }
  }));

  assert(finalSettlement !== null, "Main settlement record created for 2026-06");
  assert(parseFloat(finalSettlement.adjustmentAmount) === -650.00, "Retroactive adjustment delta matches expected clawback (-650.00)");
  assert(parseFloat(finalSettlement.totalPayout) === -470.00, "Total payout is correctly -470.00 (180 commission - 650 clawback)");

  // Verify adjustment record in DB
  const adjSettlement = await runAsAdmin(tx => tx.settlement.findFirst({
    where: {
      userId: colaboradorMde.id,
      period: '2026-06',
      originalSettlementId: { not: null }
    }
  }));
  assert(adjSettlement !== null, "Adjustment settlement record created in DB linked to original settlement ID");
  assert(parseFloat(adjSettlement.adjustmentAmount) === -650.00, "Adjustment record amount is -650.00");

  await cleanup();
  
  console.log(`\n=== E2E TEST RESULTS: ${passed} PASSED, ${failed} FAILED ===`);
  if (failed > 0) {
    process.exit(1);
  } else {
    process.exit(0);
  }
}

async function cleanup() {
  console.log("\nCleaning up E2E test browser and server processes...");
  try {
    if (browser) {
      await browser.close();
    }
  } catch (e) {}

  try {
    await cleanupDb();
    if (prisma) {
      await prisma.$disconnect();
    }
    if (pool) {
      await pool.end();
    }
  } catch (e) {
    console.error("Error during DB cleanup:", e);
  }

  if (nextProcess) {
    nextProcess.kill();
  }
}

process.on('SIGINT', () => {
  cleanup();
  process.exit(1);
});

runTests().catch(async err => {
  console.error("Fatal E2E test runner error:", err);
  if (browser) {
    try {
      const pages = await browser.pages();
      if (pages.length > 0) {
        await pages[0].screenshot({ path: path.join(SCREENSHOT_DIR, 'error_screenshot.png') });
        console.log("Saved error_screenshot.png to", path.join(SCREENSHOT_DIR, 'error_screenshot.png'));
      }
    } catch (screenshotErr) {
      console.error("Failed to capture error screenshot:", screenshotErr);
    }
  }
  await cleanup();
  process.exit(1);
});
