#!/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_AGENT_FAKE_PORT ?? 18161);
const portalPort = Number(process.env.MINDSPACE_AGENT_PORT ?? 18162);
const baseUrl = `http://127.0.0.1:${portalPort}`;
const storageRoot = path.join('/tmp', `mindspace-agent-jobs-${suffix}`);
const users = [
  {
    username: `msaj_owner_${suffix}`,
    email: `msaj-owner-${suffix}@example.test`,
    password: 'MindSpace-Agent-Owner-2026',
  },
];

const eventStreams = new Map();
const fakeSessions = new Map();

function sseWrite(res, data) {
  res.write(`data: ${JSON.stringify(data)}\n\n`);
}

const fakeUpstream = http.createServer(async (req, res) => {
  const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
  const bodyChunks = [];
  for await (const chunk of req) {
    bodyChunks.push(chunk);
  }
  const raw = bodyChunks.length ? Buffer.concat(bodyChunks).toString('utf8') : '';
  const body = raw ? JSON.parse(raw) : null;

  if (req.method === 'POST' && url.pathname === '/agent/start') {
    const sessionId = `agent-job-session-${suffix}`;
    fakeSessions.set(sessionId, {
      id: sessionId,
      working_dir: body?.working_dir ?? '/tmp',
      extensions: body?.extension_overrides ?? [],
    });
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ id: sessionId, name: 'Agent Job E2E Session' }));
    return;
  }

  const sessionMatch = url.pathname.match(/^\/sessions\/([^/]+)$/);
  if (req.method === 'GET' && sessionMatch) {
    const session = fakeSessions.get(sessionMatch[1]);
    if (!session) {
      res.statusCode = 404;
      res.end(JSON.stringify({ message: 'session not found' }));
      return;
    }
    res.setHeader('Content-Type', 'application/json');
    res.end(
      JSON.stringify({
        id: session.id,
        working_dir: session.working_dir,
      }),
    );
    return;
  }

  const extensionMatch = url.pathname.match(/^\/sessions\/([^/]+)\/extensions$/);
  if (req.method === 'GET' && extensionMatch) {
    const session = fakeSessions.get(extensionMatch[1]);
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ extensions: session?.extensions ?? [] }));
    return;
  }

  if (
    req.method === 'POST' &&
    [
      '/agent/update_working_dir',
      '/agent/update_session',
      '/agent/remove_extension',
      '/agent/add_extension',
      '/agent/restart',
      '/agent/harness_remember',
      '/agent/harness_bootstrap',
    ].includes(url.pathname)
  ) {
    res.setHeader('Content-Type', 'application/json');
    res.end('{}');
    return;
  }

  const eventMatch = url.pathname.match(/^\/sessions\/([^/]+)\/events$/);
  if (req.method === 'GET' && eventMatch) {
    const sessionId = eventMatch[1];
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    });
    res.flushHeaders?.();
    sseWrite(res, { type: 'Ping' });
    eventStreams.set(sessionId, res);
    req.on('close', () => {
      if (eventStreams.get(sessionId) === res) {
        eventStreams.delete(sessionId);
      }
    });
    return;
  }

  const replyMatch = url.pathname.match(/^\/sessions\/([^/]+)\/reply$/);
  if (req.method === 'POST' && replyMatch) {
    const sessionId = replyMatch[1];
    const stream = eventStreams.get(sessionId);
    if (!stream) {
      res.statusCode = 409;
      res.end(JSON.stringify({ message: 'events stream not connected' }));
      return;
    }
    const requestId = body?.request_id;
    const prompt = String(body?.user_message?.content?.[0]?.text ?? '');
    const content = {
      title: prompt.includes('weekly.md') ? 'Agent 生成周报' : 'Agent 页面',
      summary: '根据授权资料生成的草稿',
      content: '# Agent 周报\n\n本周已完成资产分析，并生成了草稿页面。',
      content_format: 'markdown',
    };
    setTimeout(() => {
      sseWrite(stream, {
        type: 'Message',
        request_id: requestId,
        message: {
          id: `assistant-${suffix}`,
          role: 'assistant',
          created: Math.floor(Date.now() / 1000),
          metadata: { userVisible: true, agentVisible: true },
          content: [{ type: 'text', text: JSON.stringify(content) }],
        },
        token_state: {
          inputTokens: 12,
          outputTokens: 20,
          totalTokens: 32,
          accumulatedInputTokens: 12,
          accumulatedOutputTokens: 20,
          accumulatedTotalTokens: 32,
        },
      });
      sseWrite(stream, {
        type: 'Finish',
        request_id: requestId,
        reason: 'completed',
        token_state: {
          inputTokens: 12,
          outputTokens: 20,
          totalTokens: 32,
          accumulatedInputTokens: 12,
          accumulatedOutputTokens: 20,
          accumulatedTotalTokens: 32,
        },
      });
      stream.end();
      eventStreams.delete(sessionId);
    }, 50);
    res.setHeader('Content-Type', 'application/json');
    res.end('{}');
    return;
  }

  res.statusCode = 404;
  res.setHeader('Content-Type', 'application/json');
  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,
    MINDSPACE_AGENT_JOBS_ENABLED: 'true',
  },
  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 {
      // waiting
    }
    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],
  };
}

function authHeaders(cookie) {
  return { Cookie: cookie, 'Content-Type': 'application/json' };
}

async function pollJob(cookie, jobId) {
  let lastJob = null;
  for (let attempt = 0; attempt < 60; attempt += 1) {
    const detail = await request(`/api/mindspace/v1/agent/jobs/${jobId}`, {
      headers: { Cookie: cookie },
    });
    assert.equal(detail.response.status, 200, JSON.stringify(detail.body));
    const job = detail.body.data;
    lastJob = job;
    if (['completed', 'failed', 'cancelled', 'timed_out'].includes(job.status)) {
      return job;
    }
    await new Promise((resolve) => setTimeout(resolve, 100));
  }
  throw new Error(`等待 Agent job 完成超时: ${JSON.stringify(lastJob)}`);
}

const pool = createDbPool();

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

  const space = await request('/api/mindspace/v1/space', {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(space.response.status, 200, JSON.stringify(space.body));
  const oaCategory = space.body.data.categories.find((category) => category.code === 'oa');
  assert.ok(oaCategory);

  const source = Buffer.from('# Weekly Notes\n\n- Revenue up\n- Risk controlled\n', 'utf8');
  const createdUpload = await request('/api/mindspace/v1/uploads', {
    method: 'POST',
    headers: authHeaders(owner.cookie),
    body: JSON.stringify({
      category_id: oaCategory.id,
      filename: 'weekly.md',
      size_bytes: source.length,
      declared_mime_type: 'text/markdown',
    }),
  });
  assert.equal(createdUpload.response.status, 201, JSON.stringify(createdUpload.body));

  const uploadedContent = await request(createdUpload.body.data.uploadUrl, {
    method: 'PUT',
    headers: {
      Cookie: owner.cookie,
      'Content-Type': 'application/octet-stream',
    },
    body: source,
  });
  assert.equal(uploadedContent.response.status, 200, JSON.stringify(uploadedContent.body));

  const completedUpload = await request(
    `/api/mindspace/v1/uploads/${createdUpload.body.data.id}/complete`,
    {
      method: 'POST',
      headers: authHeaders(owner.cookie),
      body: '{}',
    },
  );
  assert.equal(completedUpload.response.status, 201, JSON.stringify(completedUpload.body));
  const assetId = completedUpload.body.data.id;

  const createdJob = await request('/api/mindspace/v1/agent/jobs', {
    method: 'POST',
    headers: authHeaders(owner.cookie),
    body: JSON.stringify({
      job_type: 'generate_page',
      instruction: '请根据周报资料生成一个中文周报草稿页面',
      allowed_asset_ids: [assetId],
      output_type: 'page_draft',
    }),
  });
  assert.equal(createdJob.response.status, 201, JSON.stringify(createdJob.body));
  const jobId = createdJob.body.data.id;
  assert.equal(createdJob.body.data.status, 'queued');

  const started = await request(`/api/mindspace/v1/agent/jobs/${jobId}/run`, {
    method: 'POST',
    headers: authHeaders(owner.cookie),
    body: '{}',
  });
  assert.equal(started.response.status, 202, JSON.stringify(started.body));

  const finalJob = await pollJob(owner.cookie, jobId);
  assert.equal(finalJob.status, 'completed', JSON.stringify(finalJob));
  assert.ok(finalJob.resultPageId, '任务完成后应生成页面草稿');

  const pageDetail = await request(`/api/mindspace/v1/pages/${finalJob.resultPageId}`, {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(pageDetail.response.status, 200, JSON.stringify(pageDetail.body));
  assert.equal(pageDetail.body.data.title, 'Agent 生成周报');
  assert.match(pageDetail.body.data.content, /Agent 周报/);

  const draftPages = await request('/api/mindspace/v1/pages?status=draft', {
    headers: { Cookie: owner.cookie },
  });
  assert.equal(draftPages.response.status, 200, JSON.stringify(draftPages.body));
  assert.equal(draftPages.body.data.length, 1);

  const [usageRows] = await pool.query(
    `SELECT COUNT(*) AS count FROM h5_usage_records WHERE user_id = ?`,
    [owner.id],
  );
  assert.equal(Number(usageRows[0]?.count ?? 0), 1, 'Agent 运行后应记录一次扣费');

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