import { expect, test, type Locator, type Page, type Route } from '@playwright/test';
import { inflateSync } from 'node:zlib';

type ThemeCase = {
  name: 'light' | 'dark';
};

const themeCases: ThemeCase[] = [
  { name: 'light' },
  { name: 'dark' },
];

type Rgb = {
  r: number;
  g: number;
  b: number;
};

async function mockSignupApis(page: Page): Promise<void> {
  await page.route('**/api/v1/**', async (route: Route) => {
    const request = route.request();
    const requestUrl = new URL(request.url());
    const path = requestUrl.pathname;
    const method = request.method().toUpperCase();

    if (path.endsWith('/api/v1/user/me')) {
      await route.fulfill({
        status: 401,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'unauthorized' }),
      });
      return;
    }

    if (path.endsWith('/api/v1/auth/password/policy')) {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          minLength: 12,
          minCharacterTypes: 3,
          lowercase: true,
          uppercase: true,
          number: true,
          nonAlphanumeric: true,
        }),
      });
      return;
    }

    if (path.endsWith('/api/v1/auth/signup/check-email') && method === 'POST') {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ available: true }),
      });
      return;
    }

    if (
      (path.endsWith('/api/v1/auth/signup/send-email-code') ||
        path.endsWith('/api/v1/auth/signup/send-sms-code')) &&
      method === 'POST'
    ) {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ ok: true }),
      });
      return;
    }

    if (path.endsWith('/api/v1/auth/signup/verify-code') && method === 'POST') {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ success: true, isAffiliate: false }),
      });
      return;
    }

    if (path.endsWith('/api/v1/auth/signup') && method === 'POST') {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ ok: true }),
      });
      return;
    }

    if (path.endsWith('/api/v1/auth/tenant-info')) {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({}),
      });
      return;
    }

    if (path.endsWith('/api/v1/client-log')) {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ ok: true }),
      });
      return;
    }

    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({}),
    });
  });
}

async function enableFlutterAccessibility(page: Page): Promise<void> {
  await page.waitForTimeout(300);

  const button = page.getByRole('button', { name: 'Enable accessibility' });
  const placeholder = page.locator('flt-semantics-placeholder').first();

  await button.click({ force: true, timeout: 1_000 }).catch(async () => {
    await placeholder.click({ force: true, timeout: 1_000 }).catch(async () => {
      await placeholder.evaluate((node) => {
        (node as HTMLElement).click();
      });
    });
  });
  await page.waitForTimeout(400);
}

async function typeIntoField(page: Page, locator: Locator, value: string): Promise<void> {
  await locator.scrollIntoViewIfNeeded();
  await page.waitForTimeout(100);
  await locator.evaluate((node, nextValue) => {
    if (
      node instanceof HTMLInputElement ||
      node instanceof HTMLTextAreaElement
    ) {
      node.focus();
      node.value = '';
      node.dispatchEvent(new Event('input', { bubbles: true }));
      node.value = nextValue;
      node.dispatchEvent(new Event('input', { bubbles: true }));
      node.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }, value).catch(() => {});
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Field locator is not visible for typing.');
  }
  await page.locator('flt-glass-pane').click({
    position: {
      x: box.x + box.width / 2,
      y: box.y + box.height / 2,
    },
    force: true,
  });
  await page.waitForTimeout(100);
  await page.keyboard.press('Control+A');
  await page.keyboard.press('Backspace');
  await page.keyboard.type(value);
  await page.waitForTimeout(150);
}

async function sampleViewportColor(
  page: Page,
  x: number,
  y: number,
  radius = 2,
): Promise<Rgb> {
  const buffer = await page.screenshot();
  const image = decodePng(buffer);
  const clampedX = Math.max(0, Math.min(image.width - 1, Math.round(x)));
  const clampedY = Math.max(0, Math.min(image.height - 1, Math.round(y)));
  return sampleAverageColor(image, clampedX, clampedY, radius);
}

function decodePng(buffer: Buffer): {
  width: number;
  height: number;
  pixels: Uint8Array;
} {
  const signature = buffer.subarray(0, 8).toString('hex');
  if (signature !== '89504e470d0a1a0a') {
    throw new Error('Invalid PNG signature');
  }

  let offset = 8;
  let width = 0;
  let height = 0;
  let colorType = 0;
  const idatChunks: Buffer[] = [];

  while (offset < buffer.length) {
    const length = buffer.readUInt32BE(offset);
    const type = buffer.subarray(offset + 4, offset + 8).toString('ascii');
    const data = buffer.subarray(offset + 8, offset + 8 + length);
    offset += 12 + length;

    if (type === 'IHDR') {
      width = data.readUInt32BE(0);
      height = data.readUInt32BE(4);
      colorType = data[9];
    } else if (type === 'IDAT') {
      idatChunks.push(data);
    } else if (type === 'IEND') {
      break;
    }
  }

  if (!width || !height || ![2, 6].includes(colorType)) {
    throw new Error(`Unsupported PNG format: ${width}x${height}, color=${colorType}`);
  }

  const bytesPerPixel = colorType === 6 ? 4 : 3;
  const stride = width * bytesPerPixel;
  const inflated = inflateSync(Buffer.concat(idatChunks));
  const raw = new Uint8Array(height * stride);

  let sourceOffset = 0;
  let targetOffset = 0;

  for (let y = 0; y < height; y += 1) {
    const filter = inflated[sourceOffset];
    sourceOffset += 1;
    for (let x = 0; x < stride; x += 1) {
      const value = inflated[sourceOffset + x];
      const left = x >= bytesPerPixel ? raw[targetOffset + x - bytesPerPixel] : 0;
      const up = y > 0 ? raw[targetOffset + x - stride] : 0;
      const upLeft =
        y > 0 && x >= bytesPerPixel
          ? raw[targetOffset + x - stride - bytesPerPixel]
          : 0;
      raw[targetOffset + x] = unfilterByte(filter, value, left, up, upLeft);
    }
    sourceOffset += stride;
    targetOffset += stride;
  }

  const pixels = new Uint8Array(width * height * 4);
  for (let i = 0, j = 0; i < raw.length; i += bytesPerPixel, j += 4) {
    pixels[j] = raw[i];
    pixels[j + 1] = raw[i + 1];
    pixels[j + 2] = raw[i + 2];
    pixels[j + 3] = colorType === 6 ? raw[i + 3] : 255;
  }

  return { width, height, pixels };
}

function unfilterByte(
  filter: number,
  value: number,
  left: number,
  up: number,
  upLeft: number,
): number {
  if (filter === 0) {
    return value;
  }
  if (filter === 1) {
    return (value + left) & 0xff;
  }
  if (filter === 2) {
    return (value + up) & 0xff;
  }
  if (filter === 3) {
    return (value + Math.floor((left + up) / 2)) & 0xff;
  }
  if (filter === 4) {
    return (value + paeth(left, up, upLeft)) & 0xff;
  }
  throw new Error(`Unsupported PNG filter: ${filter}`);
}

function paeth(left: number, up: number, upLeft: number): number {
  const estimate = left + up - upLeft;
  const leftDistance = Math.abs(estimate - left);
  const upDistance = Math.abs(estimate - up);
  const upLeftDistance = Math.abs(estimate - upLeft);
  if (leftDistance <= upDistance && leftDistance <= upLeftDistance) {
    return left;
  }
  if (upDistance <= upLeftDistance) {
    return up;
  }
  return upLeft;
}

function sampleAverageColor(
  image: { width: number; height: number; pixels: Uint8Array },
  x: number,
  y: number,
  radius = 2,
): Rgb {
  const xStart = Math.max(0, Math.min(image.width - 1, x - radius));
  const xEnd = Math.max(0, Math.min(image.width - 1, x + radius));
  const yStart = Math.max(0, Math.min(image.height - 1, y - radius));
  const yEnd = Math.max(0, Math.min(image.height - 1, y + radius));

  let totalR = 0;
  let totalG = 0;
  let totalB = 0;
  let count = 0;

  for (let sampleY = yStart; sampleY <= yEnd; sampleY += 1) {
    for (let sampleX = xStart; sampleX <= xEnd; sampleX += 1) {
      const offset = (sampleY * image.width + sampleX) * 4;
      const alpha = image.pixels[offset + 3];
      if (alpha < 16) {
        continue;
      }
      totalR += image.pixels[offset];
      totalG += image.pixels[offset + 1];
      totalB += image.pixels[offset + 2];
      count += 1;
    }
  }

  if (count === 0) {
    throw new Error(`No visible pixels in sampled region at ${x}, ${y}`);
  }

  return {
    r: Math.round(totalR / count),
    g: Math.round(totalG / count),
    b: Math.round(totalB / count),
  };
}

function brightness(rgb: Rgb): number {
  return (rgb.r + rgb.g + rgb.b) / 3;
}

async function sampleLocatorColor(page: Page, locator: Locator, radius = 2): Promise<Rgb> {
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Target locator is not visible for color sampling.');
  }
  return sampleViewportColor(page, box.x + box.width / 2, box.y + box.height / 2, radius);
}

async function sampleCheckboxColor(page: Page, locator: Locator): Promise<Rgb> {
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Checkbox locator is not visible for color sampling.');
  }
  const x = box.x + Math.min(18, Math.max(12, box.width * 0.08));
  const y = box.y + box.height / 2;
  return sampleViewportColor(page, x, y, 0);
}

async function sampleButtonColor(page: Page, locator: Locator): Promise<Rgb> {
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Button locator is not visible for color sampling.');
  }
  const x = box.x + box.width * 0.2;
  const y = box.y + box.height / 2;
  return sampleViewportColor(page, x, y, 1);
}

async function sampleButtonBackground(page: Page, locator: Locator): Promise<Rgb> {
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Button locator is not visible for background sampling.');
  }
  const x = box.x + box.width / 2;
  const y = Math.max(0, box.y - 14);
  return sampleViewportColor(page, x, y, 2);
}

async function expectBrightnessContrast(
  sample: () => Promise<{ foreground: Rgb; background: Rgb }>,
  minimumDelta: number,
): Promise<void> {
  await expect
    .poll(async () => {
      const { foreground, background } = await sample();
      return Math.abs(brightness(foreground) - brightness(background));
    }, { timeout: 10_000 })
    .toBeGreaterThanOrEqual(minimumDelta);
}

async function expectButtonContrast(page: Page, locator: Locator): Promise<void> {
  await expectBrightnessContrast(async () => {
    return {
      foreground: await sampleButtonColor(page, locator),
      background: await sampleButtonBackground(page, locator),
    };
  }, 45);
}

async function sampleCheckboxBackground(page: Page, locator: Locator): Promise<Rgb> {
  const box = await locator.boundingBox();
  if (!box) {
    throw new Error('Checkbox locator is not visible for background sampling.');
  }
  const x = box.x + Math.min(42, Math.max(30, box.width * 0.18));
  const y = box.y + box.height / 2;
  return sampleViewportColor(page, x, y, 1);
}

async function expectCheckboxContrast(page: Page, locator: Locator): Promise<void> {
  await expectBrightnessContrast(async () => {
    return {
      foreground: await sampleCheckboxColor(page, locator),
      background: await sampleCheckboxBackground(page, locator),
    };
  }, 40);
}

test.describe('UserFront signup theme visibility', () => {
  for (const theme of themeCases) {
    test(`signup keeps ${theme.name} theme colors visible across steps`, async ({
      page,
    }) => {
      await mockSignupApis(page);

      if (theme.name === 'dark') {
        await page.goto('/ko/signin', { waitUntil: 'domcontentloaded' });
        await page.waitForTimeout(1200);
        await enableFlutterAccessibility(page);
        const themeToggle = page.getByRole('button', {
          name: /Light|Dark|테마 전환|Theme toggle/i,
        });
        await themeToggle.click({ force: true });
        await page.waitForTimeout(500);
      }

      await page.goto('/ko/signup', { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(1200);
      await enableFlutterAccessibility(page);

      const allAgreementCheckbox = page.getByRole('checkbox', {
        name: /모두 동의합니다|Agree to all/i,
      });
      await expect(allAgreementCheckbox).toBeVisible();
      await allAgreementCheckbox.click({ force: true });
      await expect(allAgreementCheckbox).toBeChecked();

      const nextButton = page.getByRole('button', { name: /다음 단계|Next/i });
      await expect(nextButton).toBeVisible();
      await expect(nextButton).toBeEnabled();
      await nextButton.click({ force: true });

      await expect(
        page.getByText(/본인 확인을 위해|Verify your email and phone number/i),
      ).toBeVisible();

      const emailInput = page.getByRole('textbox', {
        name: /이메일 주소|Email address/i,
      });
      const phoneInput = page.getByRole('textbox', {
        name: /휴대폰 번호|Phone number/i,
      });
      const requestButtons = page
        .getByRole('button')
        .filter({ hasText: /인증요청|재발송|Send code|Resend/i });

      await expect(emailInput).toBeVisible();
      await expect(phoneInput).toBeVisible();
      await expect(requestButtons.nth(0)).toBeVisible();
      await expect(requestButtons.nth(1)).toBeVisible();
      await expect(nextButton).toBeVisible();
    });
  }
});
