import { test, expect, type Page } from '@playwright/test';

test.use({
  baseURL: 'http://localhost:3000/',
});

// ─── Helpers ─────────────────────────────────────────────────────────────────

async function waitForMs(page: Page, ms: number) {
  await page.waitForTimeout(ms);
}

async function startFreshGame(page: Page) {
  await page.goto('/');
  await page.evaluate(() => localStorage.clear());
  await page.reload();
  await page.waitForLoadState('networkidle');
  await waitForMs(page, 3000);
}

async function clickTab(page: Page, label: string) {
  const tab = page.getByRole('tab', { name: new RegExp(label, 'i') }).first();
  await tab.click();
  await waitForMs(page, 400);
}

async function clickBtn(page: Page, text: string) {
  const btn = page.getByRole('button', { name: new RegExp(text, 'i') }).first();
  await btn.click();
  await waitForMs(page, 200);
}

async function waitForBridge(page: Page) {
  for (let attempt = 0; attempt < 30; attempt++) {
    const ready = await page.evaluate(() => !!(window as any).__TEST__);
    if (ready) return;
    await waitForMs(page, 1000);
  }
  throw new Error('Debug bridge (window.__TEST__) not available after 30s');
}

/**
 * Run n game ticks synchronously via the debug bridge.
 * Each tick advances the game by HOURS_PER_TICK (0.04) hours.
 * 50 ticks ≈ 1 in-game hour, 1200 ticks ≈ 1 in-game day.
 */
async function runTicks(page: Page, n: number) {
  await page.evaluate((count: number) => {
    (window as any).__TEST__.runTicks(count);
  }, n);
}

// ─── Test ─────────────────────────────────────────────────────────────────────

test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → Exit', () => {

  test('climb spire, fight until mana drains, gather mana, descend, exit', async ({ page }) => {
    test.setTimeout(600_000);

    const errors: string[] = [];
    page.on('console', (msg) => {
      if (msg.type() === 'error') errors.push(msg.text());
    });

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 1: Start fresh game and wait for bridge
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 1: Starting fresh game...');
    await startFreshGame(page);
    await waitForMs(page, 1500);
    await waitForBridge(page);
    console.log('[TEST] Bridge ready!');

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 2: Set up prerequisites via Debug tab UI
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 2: Setting up prerequisites via Debug tab...');
    await clickTab(page, 'debug');
    await waitForMs(page, 500);

    // ── 2a. Fill raw mana using the debug buttons ────────────────────────────
    console.log('[TEST] 2a. Filling raw mana via debug buttons...');
    const fillManaBtn = page.getByTestId('debug-mana-fill');
    await expect(fillManaBtn).toBeVisible({ timeout: 5000 });
    await fillManaBtn.click();
    await waitForMs(page, 500);

    // Add +10K several times for plenty of mana
    const plus10KBtn = page.getByTestId('debug-mana-add-10k');
    await expect(plus10KBtn).toBeVisible({ timeout: 5000 });
    for (let i = 0; i < 10; i++) {
      await plus10KBtn.click();
      await waitForMs(page, 100);
    }
    await waitForMs(page, 500);

    // ── 2b. Boost max mana via Raw Mana Mastery discipline XP ────────────────
    console.log('[TEST] 2b. Boosting max mana via Raw Mana Mastery XP...');

    // The Disciplines section is collapsed by default — expand it
    const disciplinesHeader = page.locator('button', { hasText: /^Disciplines$/ }).first();
    await disciplinesHeader.click();
    await waitForMs(page, 300);

    // Find the Raw Mana Mastery discipline row via data-testid
    const rawManaRow = page.getByTestId('debug-discipline-row-raw-mastery');
    await expect(rawManaRow).toBeVisible({ timeout: 5000 });

    // Activate Raw Mana Mastery first (discipline must exist in store before XP can be added)
    const toggleBtn = page.getByTestId('debug-discipline-toggle-raw-mastery');
    await expect(toggleBtn).toBeVisible({ timeout: 5000 });
    await toggleBtn.click();
    await waitForMs(page, 200);

    // The +1K button within that row
    const plus1KBtn = page.getByTestId('debug-discipline-add1k-raw-mastery');
    await expect(plus1KBtn).toBeVisible({ timeout: 5000 });

    // Click +1K fifteen times to get 15,000 XP
    for (let i = 0; i < 15; i++) {
      await plus1KBtn.click();
      await waitForMs(page, 50);
    }
    await waitForMs(page, 300);

    // Verify discipline XP was set via the bridge
    const rawMasteryXP = await page.evaluate(() =>
      (window as any).__TEST__.useDisciplineStore.getState().disciplines?.['raw-mastery']?.xp || 0
    );
    console.log(`[TEST] Raw Mana Mastery XP: ${rawMasteryXP}`);
    expect(rawMasteryXP).toBeGreaterThan(0);

    // ── 2c. Fill mana to max ─────────────────────────────────────────────────
    console.log('[TEST] 2c. Filling mana to max...');
    await fillManaBtn.click();
    await waitForMs(page, 500);

    const manaAfterFill = await page.evaluate(() =>
      (window as any).__TEST__.useManaStore.getState().rawMana
    );
    console.log(`[TEST] Raw mana after fill: ${manaAfterFill}`);
    expect(manaAfterFill).toBeGreaterThan(0);

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 3: Enter the Spire via "Climb the Spire" button
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 3: Entering the Spire...');
    await clickTab(page, 'spells');
    await waitForMs(page, 500);

    const climbBtn = page.getByRole('button', { name: /climb the spire/i }).first();
    await expect(climbBtn).toBeVisible({ timeout: 10000 });
    await climbBtn.click();
    await waitForMs(page, 2000);

    // Verify SpireCombatPage is showing
    await expect(page.getByText('Floor 1').first()).toBeVisible({ timeout: 10000 });
    console.log('[TEST] Spire combat page loaded!');

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 4: Fight in the Spire — run ticks to clear several rooms/floors
    // manaBolt costs 3 raw mana per cast, deals 5 damage.
    // Floor 1 HP = ~151. We run enough ticks to clear multiple floors.
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 4: Fighting in the Spire...');

    const startMana = await page.evaluate(() =>
      (window as any).__TEST__.useManaStore.getState().rawMana
    );
    const startFloor = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().currentFloor
    );
    console.log(`[TEST] Starting: Floor ${startFloor}, Mana ${startMana}`);

    // Run 6000 ticks (~2 minutes of game time, ~5 in-game hours).
    // This should clear several floors worth of enemies.
    console.log('[TEST] Running 6000 ticks of combat...');
    await runTicks(page, 6000);
    await waitForMs(page, 500); // let React re-render

    const floorAfterCombat = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().currentFloor
    );
    const manaAfterCombat = await page.evaluate(() =>
      (window as any).__TEST__.useManaStore.getState().rawMana
    );
    console.log(`[TEST] After combat: Floor ${floorAfterCombat}, Mana ${manaAfterCombat}`);
    expect(floorAfterCombat).toBeGreaterThan(startFloor);

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 5: Continue fighting to drain more mana ─────────────────────────────
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 5: Continuing combat to drain more mana...');
    await runTicks(page, 3000);
    await waitForMs(page, 500);

    const manaAfterMoreCombat = await page.evaluate(() =>
      (window as any).__TEST__.useManaStore.getState().rawMana
    );
    console.log(`[TEST] Mana after extended combat: ${manaAfterMoreCombat}`);

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 6: Descend the spire back to floor 1 ───────────────────────────────
    // Each "Climb Down" click descends one floor. We verify the floor actually
    // decrements after each click.
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 6: Descending to floor 1...');

    for (let i = 0; i < 200; i++) {
      const floorNow = await page.evaluate(() =>
        (window as any).__TEST__.useCombatStore.getState().currentFloor
      );
      if (floorNow <= 1) break;

      const climbDownBtn = page.getByRole('button', { name: /climb down/i }).first();
      const btnVisible = await climbDownBtn.isVisible({ timeout: 2000 }).catch(() => false);
      if (btnVisible) {
        await climbDownBtn.click();
        // Wait for the floor to actually decrement
        const expectedFloor = floorNow - 1;
        await page.waitForFunction(
          (target: number) => (window as any).__TEST__.useCombatStore.getState().currentFloor === target,
          expectedFloor,
          { timeout: 5000 }
        );
      } else {
        console.log('[TEST] Climb Down button not visible, breaking');
        break;
      }
    }

    const floorAfterDescend = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().currentFloor
    );
    console.log(`[TEST] Floor after descending: ${floorAfterDescend}`);
    expect(floorAfterDescend).toBe(1);

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 7: Exit the Spire ───────────────────────────────────────────────────
    // The Exit Spire button should only be visible on floor 1.
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 7: Exiting the Spire...');

    // Verify we are on floor 1 and Exit Spire button is visible
    const exitBtn = page.getByRole('button', { name: /exit spire/i }).first();
    await expect(exitBtn).toBeVisible({ timeout: 10000 });

    // Verify the button is NOT visible when not on floor 1 by checking that
    // the current floor is indeed 1 (the button's rendering condition)
    const floorBeforeExit = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().currentFloor
    );
    expect(floorBeforeExit).toBe(1);

    await exitBtn.click();
    await waitForMs(page, 2000);

    const spireModeAfterExit = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().spireMode
    );
    console.log(`[TEST] Spire mode after exit: ${spireModeAfterExit}`);
    expect(spireModeAfterExit).toBe(false);

    // Verify we are back on the main game page
    await expect(page.getByRole('tab', { name: /spells/i }).first()).toBeVisible({ timeout: 10000 });
    console.log('[TEST] Back on main game page!');

    // ══════════════════════════════════════════════════════════════════════════
    // STEP 8: Verify final state ──────────────────────────────────────────────
    // ══════════════════════════════════════════════════════════════════════════
    console.log('[TEST] Step 8: Verifying final state...');

    const maxFloorReached = await page.evaluate(() =>
      (window as any).__TEST__.useCombatStore.getState().maxFloorReached
    );
    const gameOver = await page.evaluate(() =>
      (window as any).__TEST__.useUIStore.getState().gameOver
    );

    console.log(`[TEST] MaxFloorReached: ${maxFloorReached}, GameOver: ${gameOver}`);
    expect(maxFloorReached).toBeGreaterThanOrEqual(1);
    expect(gameOver).toBe(false);

    // No React errors throughout the test
    await waitForMs(page, 1000);
    const reactErrors = errors.filter(e =>
      e.includes('React') || e.includes('Minified') || e.includes('Error #')
      || e.includes('Maximum update depth')
    );
    expect(reactErrors, `React errors: ${JSON.stringify(reactErrors)}`).toHaveLength(0);

    console.log('[TEST] ✅ Combat happy-path test passed!');
  });
});
