#!/usr/bin/env node
import { createRequire } from 'node:module';
import { execFileSync } from 'node:child_process';

const require = createRequire(import.meta.url);
const { chromium } = require('playwright');

const now = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
const username = process.env.BEAVER_SMOKE_USERNAME || `smoke${now}`;
const password = process.env.BEAVER_SMOKE_PASSWORD || 'TestPass123!';
const email = process.env.BEAVER_SMOKE_EMAIL || `${username}@example.test`;
const portalUrl = (process.env.BEAVER_SMOKE_PORTAL_URL || 'http://127.0.0.1:3081').replace(/\/$/, '');
const nextPath = process.env.BEAVER_SMOKE_NEXT_PATH || '/files';
const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined;
const hostResolverRules = process.env.BEAVER_SMOKE_HOST_RESOLVER_RULES || '';
const headless = process.env.BEAVER_SMOKE_HEADLESS !== '0';
const verifyMinio = process.env.BEAVER_SMOKE_VERIFY_MINIO === '1';

const launchArgs = [];
if (hostResolverRules) {
  launchArgs.push(`--host-resolver-rules=${hostResolverRules}`);
}

const failedResponses = [];
const failedRequests = [];

function remember(list, value) {
  list.push(value);
  if (list.length > 30) {
    list.shift();
  }
}

async function clickFirstVisible(locators) {
  for (const locator of locators) {
    if (await locator.first().isVisible().catch(() => false)) {
      await locator.first().click();
      return true;
    }
  }
  return false;
}

async function main() {
  const browser = await chromium.launch({
    headless,
    executablePath,
    args: launchArgs,
  });
  const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });

  page.on('response', (response) => {
    if (response.status() >= 400) {
      remember(failedResponses, `${response.status()} ${response.url()}`);
    }
  });
  page.on('requestfailed', (request) => {
    remember(failedRequests, `${request.url()} ${request.failure()?.errorText || ''}`);
  });

  try {
    const registerUrl = new URL('/register', portalUrl);
    registerUrl.searchParams.set('next', nextPath);
    await page.goto(registerUrl.toString(), { waitUntil: 'domcontentloaded', timeout: 30000 });

    await page.locator('#username').fill(username);
    await page.locator('#email').fill(email);
    await page.locator('#password').fill(password);
    await page.locator('#confirmPassword').fill(password);
    const submitButton = page.locator('form').first().locator('button[type="submit"]');
    await submitButton.waitFor({ state: 'visible', timeout: 30000 });
    await submitButton.click();
    await page.waitForResponse(
      (response) => response.url().includes('/api/runtime/register'),
      { timeout: 180000 },
    );

    await page.getByText(/配置模型|Model Setup/).waitFor({ timeout: 180000 });
    const skipped = await clickFirstVisible([
      page.getByRole('button', { name: /跳过|Skip/i }),
      page.locator('button.secondary-button'),
    ]);
    if (!skipped) {
      throw new Error('Model setup skip button was not found');
    }

    await page.waitForURL(new RegExp(`${nextPath.replace('/', '\\/')}$`), { timeout: 60000 });
    await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
    await page.getByText(/文件管理|Files/).waitFor({ timeout: 30000 });

    for (const root of ['uploads', 'outputs', 'shared', 'tasks']) {
      await page.getByText(root, { exact: true }).waitFor({ timeout: 30000 });
    }

    const token = await page.evaluate(() => window.localStorage.getItem('beaver_access_token') || '');
    if (!token) {
      throw new Error('No Beaver access token found after handoff');
    }
    const uploadName = `smoke-${Date.now()}.txt`;
    const uploadBody = `hello from Beaver smoke ${username}`;
    const uploadResult = await page.evaluate(async ({ token, uploadName, uploadBody }) => {
      const form = new FormData();
      form.set('path', 'uploads');
      form.set('file', new File([uploadBody], uploadName, { type: 'text/plain' }));
      const response = await fetch('/api/user-files/upload', {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
        body: form,
      });
      return { status: response.status, body: await response.json().catch(() => ({})) };
    }, { token, uploadName, uploadBody });
    if (uploadResult.status !== 200 || uploadResult.body.path !== `uploads/${uploadName}`) {
      throw new Error(`User file upload failed: ${JSON.stringify(uploadResult)}`);
    }
    const apiVisibility = await page.evaluate(async ({ token }) => {
      const headers = { Authorization: `Bearer ${token}` };
      const [status, root] = await Promise.all([
        fetch('/api/user-files/status', { headers }).then((response) => response.json()),
        fetch('/api/user-files/browse', { headers }).then((response) => response.json()),
      ]);
      return JSON.stringify({ status, root });
    }, { token });
    for (const forbidden of ['access_key', 'secret_key', 'bucket', 'namespace', 'object_prefix']) {
      if (apiVisibility.includes(forbidden)) {
        throw new Error(`User file API leaked storage field: ${forbidden}`);
      }
    }

    const minioVerified = verifyMinio ? verifyUploadedObjectInMinio(username, uploadName) : false;

    const finalUrl = page.url();
    await page.screenshot({ path: process.env.BEAVER_SMOKE_SCREENSHOT || '/tmp/beaver-auth-files-smoke.png', fullPage: true });
    console.log(JSON.stringify({
      ok: true,
      username,
      email,
      finalUrl,
      roots: ['uploads', 'outputs', 'shared', 'tasks'],
      uploadedPath: `uploads/${uploadName}`,
      minioVerified,
      failedResponses,
      failedRequests,
    }, null, 2));
  } catch (error) {
    await page.screenshot({ path: process.env.BEAVER_SMOKE_ERROR_SCREENSHOT || '/tmp/beaver-auth-files-smoke-error.png', fullPage: true }).catch(() => {});
    console.error(JSON.stringify({
      ok: false,
      username,
      email,
      currentUrl: page.url(),
      error: String(error),
      failedResponses,
      failedRequests,
    }, null, 2));
    process.exitCode = 1;
  } finally {
    await browser.close();
  }
}

function verifyUploadedObjectInMinio(username, uploadName) {
  const endpoint = process.env.BEAVER_SMOKE_MINIO_ENDPOINT || 'http://127.0.0.1:9000';
  const accessKey = process.env.BEAVER_SMOKE_MINIO_ACCESS_KEY || process.env.BEAVER_MINIO_ROOT_USER || '';
  const secretKey = process.env.BEAVER_SMOKE_MINIO_SECRET_KEY || process.env.BEAVER_MINIO_ROOT_PASSWORD || '';
  const bucket = process.env.BEAVER_SMOKE_MINIO_BUCKET || process.env.BEAVER_USER_FILES_BUCKET || 'beaver-user-files';
  const network = process.env.BEAVER_SMOKE_MINIO_NETWORK || '';
  if (!accessKey || !secretKey) {
    throw new Error('MinIO verification requested but access key or secret key is missing');
  }
  const containerArgs = ['run', '--rm', '--entrypoint', '/bin/sh'];
  if (network) {
    containerArgs.push('--network', network);
  }
  containerArgs.push(
    'minio/mc:latest',
    '-lc',
    [
      `mc alias set beaver ${shellQuote(endpoint)} ${shellQuote(accessKey)} ${shellQuote(secretKey)} >/dev/null`,
      `mc stat ${shellQuote(`beaver/${bucket}/users/${username}/uploads/${uploadName}`)} >/dev/null`,
    ].join(' && '),
  );
  execFileSync('docker', containerArgs, { stdio: 'pipe' });
  return true;
}

function shellQuote(value) {
  return `'${String(value).replace(/'/g, "'\\''")}'`;
}

await main();
