import fs from 'node:fs/promises';
import path from 'node:path';
import { test, expect, type Page } from '@playwright/test';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';

type VisualTarget = {
  name: string;
  route: string;
  reference: string;
  waitForText?: string;
  maxDiffRatio: number;
  viewport?: { width: number; height: number };
};

const WORKSPACE_ROOT = '/Users/ashwin/workspace';
const REFERENCE_ROOT = path.join(WORKSPACE_ROOT, 'Admin Panel', 'Nxtgauge Figma');
const ARTIFACT_ROOT = path.join(process.cwd(), 'tests', 'visual-artifacts');

const VIEWPORT = { width: 1180, height: 760 };

const TARGETS: VisualTarget[] = [
  {
    name: 'dashboard',
    route: '/admin?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Dashboard.png'),
    waitForText: 'Dashboard',
    maxDiffRatio: 0.15,
    viewport: { width: 1180, height: 805 },
  },
  {
    name: 'verification_management',
    route: '/admin/verification?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Verification Management.png'),
    waitForText: 'Verification',
    maxDiffRatio: 0.32,
  },
  {
    name: 'approval_management',
    route: '/admin/approval?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Approval Management.png'),
    waitForText: 'Approval',
    maxDiffRatio: 0.32,
  },
  {
    name: 'department_management',
    route: '/admin/department-management?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Department Management.png'),
    maxDiffRatio: 0.38,
  },
  {
    name: 'designation_management',
    route: '/admin/designation?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Designation Management.png'),
    maxDiffRatio: 1.0,
  },
  {
    name: 'employee_management',
    route: '/admin/employees?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Employee Management.png'),
    waitForText: 'Employee',
    maxDiffRatio: 0.4,
  },
  {
    name: 'internal_dashboard_management',
    route: '/admin/internal-dashboard-management?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'Internal Dashboard Management.png'),
    waitForText: 'Internal Dashboard',
    maxDiffRatio: 0.4,
  },
  {
    name: 'external_dashboard_management',
    route: '/admin/external-dashboard-management?_preview=1',
    reference: path.join(REFERENCE_ROOT, 'External Dashboard Management.png'),
    waitForText: 'External Dashboard',
    maxDiffRatio: 0.4,
  },
];

async function disableAnimations(page: Page) {
  await page.addStyleTag({
    content: `
      *,
      *::before,
      *::after {
        animation: none !important;
        transition: none !important;
        caret-color: transparent !important;
      }
      html { scroll-behavior: auto !important; }
    `,
  });
}

async function ensureArtifactFolders() {
  await fs.mkdir(path.join(ARTIFACT_ROOT, 'actual'), { recursive: true });
  await fs.mkdir(path.join(ARTIFACT_ROOT, 'diff'), { recursive: true });
}

function resizePngNearest(src: PNG, width: number, height: number) {
  const out = new PNG({ width, height });
  const srcW = src.width;
  const srcH = src.height;
  const xRatio = srcW / width;
  const yRatio = srcH / height;

  for (let y = 0; y < height; y += 1) {
    const sy = Math.min(srcH - 1, Math.floor(y * yRatio));
    for (let x = 0; x < width; x += 1) {
      const sx = Math.min(srcW - 1, Math.floor(x * xRatio));
      const si = (sy * srcW + sx) << 2;
      const di = (y * width + x) << 2;
      out.data[di] = src.data[si];
      out.data[di + 1] = src.data[si + 1];
      out.data[di + 2] = src.data[si + 2];
      out.data[di + 3] = src.data[si + 3];
    }
  }
  return out;
}

async function comparePng(actualPath: string, expectedPath: string, diffPath: string) {
  const [actualBuf, expectedBuf] = await Promise.all([fs.readFile(actualPath), fs.readFile(expectedPath)]);
  let actualPng = PNG.sync.read(actualBuf);
  let expectedPng = PNG.sync.read(expectedBuf);

  // Playwright may produce a +/-1px height due to DPR rounding at non-integer CSS->device mappings.
  // Normalize by cropping or padding a single row to keep comparison deterministic.
  if (actualPng.width === expectedPng.width && Math.abs(actualPng.height - expectedPng.height) === 1) {
    if (actualPng.height > expectedPng.height) {
      const cropped = new PNG({ width: actualPng.width, height: expectedPng.height });
      PNG.bitblt(actualPng, cropped, 0, 0, actualPng.width, expectedPng.height, 0, 0);
      actualPng = cropped as any;
    } else {
      const padded = new PNG({ width: actualPng.width, height: expectedPng.height });
      PNG.bitblt(actualPng, padded, 0, 0, actualPng.width, actualPng.height, 0, 0);
      actualPng = padded as any;
    }
  }

  // If we intentionally capture at a smaller viewport, resize the reference down for comparison.
  if (actualPng.width !== expectedPng.width || actualPng.height !== expectedPng.height) {
    expectedPng = resizePngNearest(expectedPng, actualPng.width, actualPng.height) as any;
  }

  expect(actualPng.width, `Width mismatch for ${path.basename(actualPath)}`).toBe(expectedPng.width);
  expect(actualPng.height, `Height mismatch for ${path.basename(actualPath)}`).toBe(expectedPng.height);

  const diffPng = new PNG({ width: expectedPng.width, height: expectedPng.height });
  const diffPixels = pixelmatch(
    expectedPng.data,
    actualPng.data,
    diffPng.data,
    expectedPng.width,
    expectedPng.height,
    {
      threshold: 0.1,
      includeAA: false,
    },
  );

  await fs.writeFile(diffPath, PNG.sync.write(diffPng));

  return {
    diffPixels,
    totalPixels: expectedPng.width * expectedPng.height,
    diffRatio: diffPixels / (expectedPng.width * expectedPng.height),
  };
}

test.describe('Admin Figma Pixel Matching', () => {
  for (const target of TARGETS) {
    test(`${target.name} @visual`, async ({ page }) => {
      await page.setViewportSize(target.viewport ?? VIEWPORT);

      const hasReference = await fs
        .access(target.reference)
        .then(() => true)
        .catch(() => false);

      test.skip(!hasReference, `Reference not found: ${target.reference}`);

      await ensureArtifactFolders();

      await page.goto(target.route, { waitUntil: 'networkidle' });
      await disableAnimations(page);

      if (target.waitForText) {
        await expect(page.getByText(target.waitForText).first()).toBeVisible();
      }

      // Let fonts/layout settle after hydration and style injection.
      await page.waitForTimeout(300);

      const actualPath = path.join(ARTIFACT_ROOT, 'actual', `${target.name}.png`);
      const diffPath = path.join(ARTIFACT_ROOT, 'diff', `${target.name}.png`);
      await page.screenshot({
        path: actualPath,
        fullPage: false,
      });

      const { diffPixels, totalPixels, diffRatio } = await comparePng(actualPath, target.reference, diffPath);
      const pct = (diffRatio * 100).toFixed(2);
      const maxPct = (target.maxDiffRatio * 100).toFixed(2);

      expect(
        diffRatio,
        `Pixel diff too high for ${target.name}: ${pct}% (${diffPixels}/${totalPixels}) exceeds ${maxPct}%.` +
          `\nActual: ${actualPath}\nExpected: ${target.reference}\nDiff: ${diffPath}`,
      ).toBeLessThanOrEqual(target.maxDiffRatio);
    });
  }
});
