/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { test, expect } from '../playwright-test/stable-test-runner';
import { PNG } from 'playwright-core/lib/utilsBundle';
import { compare } from 'playwright-core/lib/server/utils/image_tools/compare';
import fs from 'fs';
import path from 'path';

function listFixtures(root: string, fixtures: Set<string> = new Set()) {
  for (const item of fs.readdirSync(root, { withFileTypes: true })) {
    const p = path.join(root, item.name);
    if (item.isDirectory())
      listFixtures(p, fixtures);
    else if (item.isFile() && p.endsWith('-actual.png'))
      fixtures.add(p.substring(0, p.length - '-actual.png'.length));
  }
  return fixtures;
}

const FIXTURES_DIR = path.join(__dirname, 'fixtures');

function declareFixtureTest(fixtureRoot: string, fixtureName: string, shouldMatch: boolean) {
  test(path.relative(fixtureRoot, fixtureName), async ({}, testInfo) => {
    const [actual, expected] = await Promise.all([
      fs.promises.readFile(fixtureName + '-actual.png'),
      fs.promises.readFile(fixtureName + '-expected.png'),
    ]);
    await testInfo.attach(fixtureName + '-actual.png', {
      body: actual,
      contentType: 'image/png',
    });
    await testInfo.attach(fixtureName + '-expected.png', {
      body: expected,
      contentType: 'image/png',
    });
    const actualPNG = PNG.sync.read(actual);
    const expectedPNG = PNG.sync.read(expected);
    expect(actualPNG.width).toBe(expectedPNG.width);
    expect(actualPNG.height).toBe(expectedPNG.height);

    const diffPNG = new PNG({ width: actualPNG.width, height: actualPNG.height });
    const diffCount = compare(actualPNG.data, expectedPNG.data, diffPNG.data, actualPNG.width, actualPNG.height, {
      maxColorDeltaE94: 1.0,
    });

    await testInfo.attach(fixtureName + '-diff.png', {
      body: PNG.sync.write(diffPNG),
      contentType: 'image/png',
    });

    if (shouldMatch)
      expect(diffCount).toBe(0);
    else
      expect(diffCount).not.toBe(0);
  });
}

test.describe('basic fixtures', () => {
  test.describe.configure({ mode: 'parallel' });

  for (const fixtureName of listFixtures(path.join(FIXTURES_DIR, 'should-match')))
    declareFixtureTest(FIXTURES_DIR, fixtureName, true /* shouldMatch */);
  for (const fixtureName of listFixtures(path.join(FIXTURES_DIR, 'should-fail')))
    declareFixtureTest(FIXTURES_DIR, fixtureName, false /* shouldMatch */);
});

const customImageToolsFixtures = process.env.IMAGE_TOOLS_FIXTURES;
if (customImageToolsFixtures) {
  test.describe('custom fixtures', () => {
    test.describe.configure({ mode: 'parallel' });

    for (const fixtureName of listFixtures(path.join(customImageToolsFixtures, 'should-match')))
      declareFixtureTest(customImageToolsFixtures, fixtureName, true /* shouldMatch */);
    for (const fixtureName of listFixtures(path.join(customImageToolsFixtures, 'should-fail')))
      declareFixtureTest(customImageToolsFixtures, fixtureName, false /* shouldMatch */);
  });
}
