import fs from 'node:fs/promises';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { test as base, expect, _electron as electron, type ElectronApplication, type Page } from '@playwright/test';

const require = createRequire(import.meta.url);
const electronExecutable = require('electron') as string;

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const mainEntry = path.join(root, 'dist/main/index.cjs');
const fixturePng = path.join(root, 'e2e/fixtures/sample.png');

const SAMPLE_PNG_B64 =
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';

export type ElectronFixtures = {
  electronApp: ElectronApplication;
  editorWindow: Page;
  userDataDir: string;
  sampleImagePath: string;
};

async function ensureBuilt() {
  try {
    await fs.access(mainEntry);
    const editorHtml = await fs.readFile(path.join(root, 'dist/renderer/editor.html'), 'utf8');
    // Production Vite build uses relative asset URLs (required for Electron file://).
    if (editorHtml.includes('src="/assets/') || editorHtml.includes("src='/assets/")) {
      throw new Error('dev-base');
    }
  } catch (err) {
    const reason = err instanceof Error && err.message === 'dev-base' ? 'dev-base' : 'missing';
    throw new Error(
      reason === 'dev-base'
        ? 'dist/renderer was built with Vite base "/". Run "npm run build" (production) before e2e.'
        : `Missing ${mainEntry}. Run "npm run build" (or "npm run test:e2e:build") before e2e.`,
    );
  }
}

export const test = base.extend<ElectronFixtures>({
  userDataDir: async ({}, use) => {
    const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-e2e-'));
    await use(dir);
    await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
  },

  sampleImagePath: async ({}, use) => {
    await fs.mkdir(path.dirname(fixturePng), { recursive: true });
    await fs.writeFile(fixturePng, Buffer.from(SAMPLE_PNG_B64, 'base64'));
    await use(fixturePng);
  },

  electronApp: async ({ userDataDir }, use) => {
    await ensureBuilt();
    const app = await electron.launch({
      executablePath: electronExecutable,
      // Launch the package root so app.getAppPath() is the repo (not dist/main).
      args: ['.', `--user-data-dir=${userDataDir}`],
      cwd: root,
      env: {
        ...process.env,
        NODE_ENV: 'production',
        DND_SKIP_LICENSE: '1',
        DND_SKIP_BOOT: '1',
        ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
      },
    });
    await use(app);
    try {
      await app.evaluate(({ app: electronApp }) => {
        electronApp.exit(0);
      });
    } catch {
      /* ignore */
    }
    try {
      await Promise.race([
        app.close(),
        new Promise<void>((resolve) => {
          setTimeout(resolve, 3_000);
        }),
      ]);
    } catch {
      /* ignore */
    }
  },

  editorWindow: async ({ electronApp }, use) => {
    const page = await electronApp.firstWindow();
    await page.waitForLoadState('domcontentloaded');
    await expect(page.getByTestId('players-header-btn')).toBeVisible({ timeout: 60_000 });
    await use(page);
  },
});

export { expect };

/** Stub native open-dialog to return a fixed image path. */
export async function stubOpenImageDialog(app: ElectronApplication, filePath: string) {
  await app.evaluate(async ({ dialog }, chosen) => {
    dialog.showOpenDialog = async () => ({ canceled: false, filePaths: [chosen] });
  }, filePath);
}

export async function invokeInRenderer<T>(
  page: Page,
  channel: string,
  payload: unknown = {},
): Promise<T> {
  return page.evaluate(
    async ({ channel: ch, payload: pl }) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      return (window as any).dnd.invoke(ch, pl);
    },
    { channel, payload },
  );
}
