#!/usr/bin/env node
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import http from 'node:http';
import path from 'node:path';
import { createDbPool } from '../db.mjs';
import { loadH5Environment } from './load-env.mjs';

loadH5Environment(import.meta.dirname);

const suffix = `${Date.now()}${Math.floor(Math.random() * 1000)}`;
const fakePort = Number(process.env.MINDSPACE_PAGE_FAKE_PORT ?? 18121);
const portalPort = Number(process.env.MINDSPACE_PAGE_PORT ?? 18122);
const baseUrl = `http://127.0.0.1:${portalPort}`;
const storageRoot = path.join('/tmp', `mindspace-pages-${suffix}`);
const sessionId = `page-e2e-session-${suffix}`;
const messageId = `assistant-message-${suffix}`;
const sourceContent = '# 安全页面\n\n这是来自 AI 会话的正文。\n\n<script>alert("blocked")</script>';
const users = [
  {
    username: `msp_owner_${suffix}`,
    email: `msp-owner-${suffix}@example.test`,
    password: 'MindSpace-Page-Owner-2026',
  },
  {
    username: `msp_other_${suffix}`,
    email: `msp-other-${suffix}@example.test`,
    password: 'MindSpace-Page-Other-2026',
  },
];

const fakeUpstream = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === `/sessions/${sessionId}`) {
    res.setHeader('Content-Type', 'application/json');
    res.end(
      JSON.stringify({
        id: sessionId,
        name: '页面 E2E 会话',
        conversation: [
          {
            id: messageId,
            role: 'assistant',
            created: Math.floor(Date.now() / 1000),
            metadata: { userVisible: true, agentVisible: true },
            content: [{ type: 'text', text: sourceContent }],
          },
        ],
      }),
    );
    return;
  }
  res.statusCode = 404;
  res.end(JSON.stringify({ message: 'not found' }));
});

await new Promise((resolve) => fakeUpstream.listen(fakePort, '127.0.0.1', resolve));

const portal = spawn(process.execPath, ['server.mjs'], {
  cwd: path.join(import.meta.dirname, '..'),
  env: {
    ...process.env,
    H5_PORT: String(portalPort),
    TKMIND_API_TARGET: `http://127.0.0.1:${fakePort}`,
    MINDSPACE_STORAGE_ROOT: storageRoot,
  },
  stdio: ['ignore', 'pipe', 'pipe'],
});
let portalLogs = '';
portal.stdout.on('data', (chunk) => {
  portalLogs += chunk.toString();
});
portal.stderr.on('data', (chunk) => {
  portalLogs += chunk.toString();
});

async function waitForPortal() {
  for (let attempt = 0; attempt < 60; attempt += 1) {
    if (portal.exitCode != null) throw new Error(`Portal 提前退出\n${portalLogs}`);
    try {
      const response = await fetch(`${baseUrl}/auth/status`);
      if (response.ok) return;
    } catch {
      // Portal is still starting.
    }
    await new Promise((resolve) => setTimeout(resolve, 100));
  }
  throw new Error(`Portal 启动超时\n${portalLogs}`);
}

async function request(pathname, options = {}) {
  const response = await fetch(`${baseUrl}${pathname}`, options);
  const contentType = response.headers.get('content-type') ?? '';
  const body = contentType.includes('application/json')
    ? await response.json()
    : await response.text();
  return { response, body };
}

async function registerAndLogin(user) {
  const registration = await request('/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...user, displayName: user.username }),
  });
  assert.equal(registration.response.status, 200, JSON.stringify(registration.body));
  const login = await request('/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: user.username, password: user.password }),
  });
  assert.equal(login.response.status, 200, JSON.stringify(login.body));
  return {
    id: registration.body.user.id,
    cookie: login.response.headers.get('set-cookie')?.split(';', 1)[0],
  };
}

const pool = createDbPool();

try {
  await waitForPortal();
  const owner = await registerAndLogin(users[0]);
  const other = await registerAndLogin(users[1]);
  assert.ok(owner.cookie && other.cookie);

  await pool.query(
    `INSERT INTO h5_user_sessions (agent_session_id, user_id, created_at) VALUES (?, ?, ?)`,
    [sessionId, owner.id, Date.now()],
  );

  const created = await request('/api/mindspace/v1/pages/save-from-chat', {
    method: 'POST',
    headers: {
      Cookie: owner.cookie,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      session_id: sessionId,
      message_id: messageId,
      title: 'AI 会话页面',
      summary: '由会话生成的安全页面',
      template_id: 'report',
    }),
  });
  assert.equal(created.response.status, 201, JSON.stringify(created.body));
  const pageId = created.body.data.page.id;
  assert.equal(created.body.data.kind, 'page');
  assert.equal(created.body.data.page.versionNo, 1);
  assert.equal(created.body.data.page.content, sourceContent);

  const detail = await request(`/api/mindspace/v1/pages/${pageId}`, {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(detail.response.status, 200);
  assert.equal(detail.body.data.sourceSessionId, sessionId);
  assert.equal(detail.body.data.versions.length, 1);

  const preview = await request(`/api/mindspace/v1/pages/${pageId}/preview`, {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(preview.response.status, 200);
  assert.match(preview.body, /default-src 'none'/);
  assert.doesNotMatch(preview.body, /<script>alert/);
  assert.match(preview.body, /&lt;script&gt;/);

  const updatedContent = `${sourceContent}\n\n第二版补充内容。`;
  const updated = await request(`/api/mindspace/v1/pages/${pageId}`, {
    method: 'PUT',
    headers: {
      Cookie: owner.cookie,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      expected_version: 1,
      title: 'AI 会话页面 v2',
      summary: '第二版摘要',
      content: updatedContent,
      template_id: 'knowledge-card',
      change_note: 'E2E 第二版',
    }),
  });
  assert.equal(updated.response.status, 200, JSON.stringify(updated.body));
  assert.equal(updated.body.data.versionNo, 2);

  const staleUpdate = await request(`/api/mindspace/v1/pages/${pageId}`, {
    method: 'PUT',
    headers: {
      Cookie: owner.cookie,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      expected_version: 1,
      title: '过期修改',
      content: '不应写入',
      template_id: 'editorial',
    }),
  });
  assert.equal(staleUpdate.response.status, 409);
  assert.equal(staleUpdate.body.error.code, 'version_conflict');

  for (const pathname of [
    `/api/mindspace/v1/pages/${pageId}`,
    `/api/mindspace/v1/pages/${pageId}/preview`,
  ]) {
    const foreignRead = await request(pathname, { headers: { Cookie: other.cookie } });
    assert.equal(foreignRead.response.status, 404);
  }

  const foreignUpdate = await request(`/api/mindspace/v1/pages/${pageId}`, {
    method: 'PUT',
    headers: {
      Cookie: other.cookie,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      expected_version: 2,
      title: '越权修改',
      content: '禁止',
      template_id: 'editorial',
    }),
  });
  assert.equal(foreignUpdate.response.status, 404);

  const space = await request('/api/mindspace/v1/space', {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(space.response.status, 200);
  assert.equal(
    space.body.data.categories.find((category) => category.code === 'draft').itemCount,
    1,
  );
  assert.equal(
    space.body.data.quota.usedBytes,
    Buffer.byteLength(sourceContent) + Buffer.byteLength(updatedContent),
  );

  console.log('MindSpace page API E2E passed');
} finally {
  await pool.query(`DELETE FROM h5_users WHERE username IN (?, ?)`, [
    users[0].username,
    users[1].username,
  ]);
  await pool.end();
  portal.kill('SIGTERM');
  await new Promise((resolve) => portal.once('exit', resolve));
  await new Promise((resolve) => fakeUpstream.close(resolve));
  await fs.rm(storageRoot, { recursive: true, force: true });
}
