import { expect, test, type APIRequestContext } from '@playwright/test';
import { Wallet } from 'ethers';
import { createWalletContext } from '../setup/wallet-provider';
import { getStackConfig, validateStackHealthy } from '../setup/stack';
import { navigateSPA } from '../setup/navigate';
import { TAX_RATE_OPTIONS } from '../../kraiken-lib/src/taxRates';

const ACCOUNT_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
const ACCOUNT_ADDRESS = new Wallet(ACCOUNT_PRIVATE_KEY).address.toLowerCase();

// Get stack configuration from environment (or defaults)
const STACK_CONFIG = getStackConfig();
const STACK_RPC_URL = STACK_CONFIG.rpcUrl;
const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl;
const STACK_GRAPHQL_URL = STACK_CONFIG.graphqlUrl;

async function fetchPositions(request: APIRequestContext, owner: string) {
  const response = await request.post(STACK_GRAPHQL_URL, {
    data: {
      query: `
        query PositionsByOwner($owner: String!) {
          positionss(where: { owner: $owner }, limit: 100) {
            items {
              id
              owner
              taxRate
              stakeDeposit
              status
            }
          }
        }
      `,
      variables: { owner },
    },
    headers: { 'content-type': 'application/json' },
  });

  expect(response.ok()).toBeTruthy();
  const payload = await response.json();
  return (payload?.data?.positionss?.items ?? []) as Array<{
    id: string;
    owner: string;
    taxRate: number;
    stakeDeposit: string;
    status: string;
  }>;
}

async function fetchStats(request: APIRequestContext) {
  const response = await request.post(STACK_GRAPHQL_URL, {
    data: {
      query: `
        query Stats {
          stats(id: "0x01") {
            kraikenTotalSupply
            kraikenStakedSupply
            percentageStaked
          }
        }
      `,
    },
    headers: { 'content-type': 'application/json' },
  });

  expect(response.ok()).toBeTruthy();
  const payload = await response.json();
  return payload?.data?.stats;
}

test.describe('Max Stake All Tax Rates', () => {
  test.beforeAll(async () => {
    await validateStackHealthy(STACK_CONFIG);
  });

  test('fills all tax rates until maxStake is reached', async ({ browser, request }) => {
    test.setTimeout(10 * 60 * 1000); // 10 minutes — this test creates 30 staking positions via UI
    console.log('[TEST] Creating wallet context...');
    const context = await createWalletContext(browser, {
      privateKey: ACCOUNT_PRIVATE_KEY,
      rpcUrl: STACK_RPC_URL,
    });

    const page = await context.newPage();

    // Log browser console messages
    page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`));
    page.on('pageerror', error => console.log(`[BROWSER ERROR] ${error.message}`));

    try {
      console.log('[TEST] Loading app...');
      await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' });
      console.log('[TEST] App loaded, waiting for Vue app to mount...');

      // Wait for the Vue app to fully mount by waiting for a key element
      // The navbar-title is always present regardless of connection state
      const navbarTitle = page.locator('.navbar-title').first();
      await expect(navbarTitle).toBeVisible({ timeout: 30_000 });
      console.log('[TEST] Vue app mounted, navbar is visible');

      // Trigger a resize event to force Vue's useMobile composable to recalculate
      // This ensures the app recognizes the desktop screen width set by wallet-provider
      await page.evaluate(() => {
        window.dispatchEvent(new Event('resize'));
      });
      // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
      await page.waitForTimeout(500);

      // Give extra time for wallet connectors to initialize
      // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
      await page.waitForTimeout(2_000);

      // Connect wallet flow:
      // The wallet-provider sets screen.width to 1280 to ensure desktop mode.
      // We expect the desktop Connect button to be visible.
      console.log('[TEST] Looking for Connect button...');

      // Check if wallet already connected (wagmi may auto-reconnect from storage)
      const alreadyConnected = page.locator('.connect-button--connected').first();
      if (await alreadyConnected.isVisible({ timeout: 1_000 }).catch(() => false)) {
        console.log('[TEST] Wallet already connected (auto-reconnect), skipping connect flow');
      } else {
        // Desktop Connect button — wait up to 10s for wagmi to settle into disconnected state
        const connectButton = page.locator('.connect-button--disconnected').first();

        let panelOpened = false;

        if (await connectButton.isVisible({ timeout: 10_000 })) {
          console.log('[TEST] Found desktop Connect button, clicking...');
          await connectButton.click();
          panelOpened = true;
        } else {
          // Debug: Log current screen.width and navbar-end contents
          const screenWidth = await page.evaluate(() => window.screen.width);
          const navbarEndHtml = await page.locator('.navbar-end').innerHTML().catch(() => 'not found');
          console.log(`[TEST] DEBUG: screen.width = ${screenWidth}`);
          console.log(`[TEST] DEBUG: navbar-end HTML = ${navbarEndHtml.substring(0, 500)}`);
          console.log('[TEST] Connect button not visible - checking for mobile fallback...');

          // Fallback to mobile login icon (SVG in navbar-end when disconnected)
          const mobileLoginIcon = page.locator('.navbar-end svg').first();
          if (await mobileLoginIcon.isVisible({ timeout: 2_000 })) {
            console.log('[TEST] Found mobile login icon, clicking...');
            await mobileLoginIcon.click();
            panelOpened = true;
          } else {
            console.log('[TEST] No Connect button or mobile icon visible - wallet may already be connected');
          }
        }

        if (panelOpened) {
          // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
          await page.waitForTimeout(1_000);

          // Look for the injected wallet connector in the slideout panel
          console.log('[TEST] Looking for wallet connector in panel...');
          const injectedConnector = page.locator('.connectors-element').first();
          if (await injectedConnector.isVisible({ timeout: 5_000 })) {
            console.log('[TEST] Clicking first wallet connector...');
            await injectedConnector.click();
            // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
            await page.waitForTimeout(2_000);
          } else {
            console.log('[TEST] WARNING: No wallet connector found in panel');
          }
        }
      }

      // Verify wallet connection
      console.log('[TEST] Checking for wallet display...');
      const walletDisplay = page.getByText(/0xf39F/i).first();
      await expect(walletDisplay).toBeVisible({ timeout: 15_000 });
      console.log('[TEST] Wallet connected successfully!');

      // Step 1: Mint test ETH
      console.log('[TEST] Navigating to cheats page...');
      await navigateSPA(page, '/app/cheats');
      await expect(page.getByRole('heading', { name: 'Cheat Console' })).toBeVisible({ timeout: 10_000 });

      console.log('[TEST] Minting test ETH (10 ETH)...');
      await page.getByLabel('RPC URL').fill(STACK_RPC_URL);
      await page.getByLabel('Recipient').fill(ACCOUNT_ADDRESS);
      const mintButton = page.getByRole('button', { name: 'Mint' });
      await expect(mintButton).toBeEnabled();
      await mintButton.click();
      // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
      await page.waitForTimeout(3_000);

      // Step 2: Buy a large amount of KRK tokens via the get-krk page
      console.log('[TEST] Navigating to get-krk page to buy KRK...');
      await navigateSPA(page, '/app/get-krk');
      await expect(page.getByRole('heading', { name: 'Get $KRK Tokens' })).toBeVisible({ timeout: 10_000 });

      console.log('[TEST] Buying KRK tokens (swapping 5 ETH)...');
      const ethToSpendInput = page.getByTestId('swap-amount-input');
      await expect(ethToSpendInput).toBeVisible({ timeout: 15_000 });
      await ethToSpendInput.fill('5');

      const buyButton = page.getByTestId('swap-buy-button');
      await expect(buyButton).toBeVisible();
      await buyButton.click();

      // Wait for swap to complete. The button becomes disabled (swapping=true) while the
      // transaction is in flight and re-enables (swapping=false) when it finishes.
      // Try to observe the disabled state first; if the RPC responds so fast that the
      // disabled state cycles before we can observe it, the try throws and we fall through.
      // Either way, the unconditional toBeEnabled() call below waits for the final ready
      // state, covering both fast-RPC (already enabled) and slow-RPC (waiting to enable) paths.
      console.log('[TEST] Waiting for swap to process...');
      try {
        await expect(buyButton).toBeDisabled({ timeout: 5_000 });
      } catch {
        console.log('[TEST] Swap completed before disabled state was observable (fast RPC).');
      }
      await expect(buyButton).toBeEnabled({ timeout: 60_000 });
      console.log('[TEST] Swap completed!');
      // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
      await page.waitForTimeout(2_000);

      // Verify we have KRK tokens
      console.log('[TEST] Verifying KRK balance via RPC...');
      const balanceResponse = await fetch(STACK_RPC_URL, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          jsonrpc: '2.0',
          id: 1,
          method: 'eth_call',
          params: [{
            to: STACK_CONFIG.contracts.Kraiken, // KRK token from deployments
            data: `0x70a08231000000000000000000000000${ACCOUNT_ADDRESS.slice(2)}` // balanceOf(address)
          }, 'latest']
        })
      });

      const balanceData = await balanceResponse.json();
      const balance = BigInt(balanceData.result || '0x0');
      console.log(`[TEST] KRK balance: ${balance.toString()} wei`);
      expect(balance).toBeGreaterThan(0n);

      // Step 3: Navigate to stake page
      console.log('[TEST] Navigating to stake page...');
      await navigateSPA(page, '/app/stake');
      // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
      await page.waitForTimeout(2_000);

      const tokenAmountSlider = page.getByRole('slider', { name: 'Token Amount' });
      await expect(tokenAmountSlider).toBeVisible({ timeout: 15_000 });

      // Step 4: Create staking positions for all tax rates
      console.log(`[TEST] Creating positions for all ${TAX_RATE_OPTIONS.length} tax rates...`);

      let positionCount = 0;
      let maxStakeReached = false;

      // We'll try to create positions with increasing amounts to fill maxStake faster
      const baseStakeAmount = 1000;

      for (const taxRateOption of TAX_RATE_OPTIONS) {
        if (maxStakeReached) break;

        console.log(`[TEST] Creating position with tax rate ${taxRateOption.index} (${taxRateOption.year}% annually)...`);

        // Fill stake form
        const stakeAmountInput = page.getByLabel('Staking Amount');
        await expect(stakeAmountInput).toBeVisible({ timeout: 10_000 });
        await stakeAmountInput.fill(baseStakeAmount.toString());

        const taxSelect = page.getByRole('combobox', { name: 'Tax' });
        await taxSelect.selectOption({ value: taxRateOption.index.toString() });

        // Click stake button
        const stakeButton = page.getByRole('main').getByRole('button', { name: /Stake|Snatch and Stake/i });
        await expect(stakeButton).toBeVisible({ timeout: 5_000 });

        // Check if staking is still possible (button might be disabled if maxStake reached)
        const isDisabled = await stakeButton.isDisabled();
        if (isDisabled) {
          console.log('[TEST] Stake button is disabled - likely maxStake reached');
          maxStakeReached = true;
          break;
        }

        await stakeButton.click();

        // Wait for transaction to process
        try {
          await page.getByRole('button', { name: /Sign Transaction|Waiting/i }).waitFor({ state: 'visible', timeout: 5_000 });
          await page.getByRole('button', { name: /Stake|Snatch and Stake/i }).waitFor({ state: 'visible', timeout: 60_000 });
        } catch (e) {
          // Transaction completed instantly or failed
          // Check if we got an error message
          const errorVisible = await page.getByText(/error|failed|exceeded/i).isVisible().catch(() => false);
          if (errorVisible) {
            console.log('[TEST] Transaction failed - likely maxStake reached');
            maxStakeReached = true;
            break;
          }
        }

        positionCount++;
        // eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles.
        await page.waitForTimeout(2_000);
      }

      console.log(`[TEST] Created ${positionCount} positions`);

      // Step 5: Verify positions via GraphQL
      console.log('[TEST] Verifying positions via GraphQL...');
      const positions = await fetchPositions(request, ACCOUNT_ADDRESS);
      console.log(`[TEST] Found ${positions.length} position(s) in GraphQL`);

      const activePositions = positions.filter(p => p.status === 'Active');
      console.log(`[TEST] ${activePositions.length} active positions`);

      expect(activePositions.length).toBeGreaterThan(0);

      // Verify we have positions with different tax rates
      const uniqueTaxRates = new Set(activePositions.map(p => p.taxRate));
      console.log(`[TEST] Unique tax rates used: ${uniqueTaxRates.size}`);

      // We should have created positions for most/all tax rates (may have pre-existing positions from other tests)
      expect(positionCount).toBeGreaterThan(20);
      expect(uniqueTaxRates.size).toBeGreaterThan(20);

      // Step 6: Verify maxStake constraint
      console.log('[TEST] Verifying maxStake constraint...');
      const stats = await fetchStats(request);
      console.log(`[TEST] Staked percentage: ${stats?.percentageStaked}`);

      if (stats?.percentageStaked) {
        const percentageStaked = parseFloat(stats.percentageStaked);
        // percentageStaked is a ratio (0-1), maxStake is 20%
        expect(percentageStaked).toBeLessThanOrEqual(1.0);
        console.log(`[TEST] ✅ MaxStake constraint respected: ${(percentageStaked * 100).toFixed(2)}% staked`);
      }

      console.log(`[TEST] ✅ Test complete: Created ${activePositions.length} positions across ${uniqueTaxRates.size} different tax rates`);

      // Take screenshot of final stake page with all positions
      console.log('[TEST] Taking screenshot of stake page...');
      await page.screenshot({ path: 'test-results/stake-page-with-all-positions.png', fullPage: true });
      console.log('[TEST] Screenshot saved to test-results/stake-page-with-all-positions.png');
    } finally {
      await context.close();
    }
  });
});
