import assert from 'node:assert/strict';
import http from 'node:http';
import pg from 'pg';
import { spawn } from 'node:child_process';

const databaseUrl = process.env.DATABASE_URL || 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
const tenantId = '00000000-0000-0000-0000-000000000001';
const adminUserId = '00000000-0000-0000-0000-000000000102';
const studentUserId = '00000000-0000-0000-0000-000000000101';
const salesUserId = '00000000-0000-0000-0000-000000000104';

function getFreePort() {
  return new Promise((resolve, reject) => {
    const server = http.createServer();
    server.listen(0, '127.0.0.1', () => {
      const address = server.address();
      server.close(() => resolve(address.port));
    });
    server.on('error', reject);
  });
}

async function startWebhookServer() {
  const port = await getFreePort();
  const requests = [];
  const server = http.createServer((req, res) => {
    let raw = '';
    req.on('data', chunk => {
      raw += chunk.toString();
    });
    req.on('end', () => {
      requests.push({
        url: req.url,
        headers: req.headers,
        body: raw ? JSON.parse(raw) : {},
      });
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({ ok: true }));
    });
  });
  await new Promise(resolve => server.listen(port, '127.0.0.1', resolve));
  return {
    url: `http://127.0.0.1:${port}/crm`,
    requests,
    close: () => new Promise(resolve => server.close(resolve)),
  };
}

async function runWorkerOnce() {
  const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'crm'], {
    cwd: process.cwd(),
    env: {
      ...process.env,
      DATABASE_URL: databaseUrl,
      WORKER_CRM_ALLOW_INSECURE_LOCALHOST: 'true',
      WORKER_CRM_REQUEST_TIMEOUT_MS: '5000',
    },
    stdio: ['ignore', 'pipe', 'pipe'],
    windowsHide: true,
  });
  let output = '';
  child.stdout.on('data', chunk => {
    output += chunk.toString();
  });
  child.stderr.on('data', chunk => {
    output += chunk.toString();
  });
  const code = await new Promise(resolve => child.on('exit', resolve));
  assert.equal(code, 0, `worker should exit 0\n${output}`);
  assert.match(output, /processed=\d+/, 'worker output should include processed count');
}

async function main() {
  const webhook = await startWebhookServer();
  const pool = new pg.Pool({ connectionString: databaseUrl });
  try {
    await pool.query('begin');
    await pool.query(
      `
      delete from public.crm_webhook_log
      where tenant_id = $1 and record_id = $2
      `,
      [tenantId, studentUserId],
    );
    await pool.query(
      `
      delete from public.crm_webhook_queue
      where tenant_id = $1 and idempotency_key = 'lead:worker-integration'
      `,
      [tenantId],
    );
    await pool.query(
      `
      insert into app_private.tenant_secrets (tenant_id, secret_scope, secret_key, secret_value, provider, last_rotated_at)
      values ($1, 'crm', 'webhook', 'crm-secret-worker-test', 'webhook', now())
      on conflict (tenant_id, secret_scope, secret_key)
      do update set secret_value = excluded.secret_value,
                    provider = excluded.provider,
                    last_rotated_at = now(),
                    updated_at = now()
      `,
      [tenantId],
    );
    await pool.query(
      `
      insert into public.crm_config (
        tenant_id, enabled, url, secret_ref, form_name, exam_type, timeout_sec, delay_sec
      )
      values ($1, true, $2, 'app_private.tenant_secrets:crm:webhook', 'Worker集成测试客资', '专升本', 5, 0)
      on conflict (tenant_id)
      do update set enabled = excluded.enabled,
                    url = excluded.url,
                    secret_ref = excluded.secret_ref,
                    form_name = excluded.form_name,
                    exam_type = excluded.exam_type,
                    timeout_sec = excluded.timeout_sec,
                    delay_sec = excluded.delay_sec,
                    updated_at = now()
      `,
      [tenantId, webhook.url],
    );
    await pool.query(
      `
      insert into public.referral_leads (
        tenant_id, student_user_id, referrer_user_id, ref_code, source, bind_type, status, metadata
      )
      values ($1, $2, $3, 'WORKER1'::citext, 'integration', 'manual', 'protected', '{"source":"crm-worker-test"}'::jsonb)
      on conflict (tenant_id, student_user_id)
      do update set referrer_user_id = excluded.referrer_user_id,
                    ref_code = excluded.ref_code,
                    source = excluded.source,
                    bind_type = excluded.bind_type,
                    status = excluded.status,
                    updated_at = now()
      `,
      [tenantId, studentUserId, salesUserId],
    );
    const lead = await pool.query(
      `
      select id, bound_at
      from public.referral_leads
      where tenant_id = $1 and student_user_id = $2
      limit 1
      `,
      [tenantId, studentUserId],
    );
    await pool.query(
      `
      insert into public.crm_webhook_queue (
        tenant_id, record_id, status, scheduled_at, lead_id, source,
        payload, idempotency_key, target_url
      )
      values (
        $1, $2, 'pending', now() - interval '1 second', $3, 'integration',
        $4::jsonb, 'lead:worker-integration', $5
      )
      on conflict (tenant_id, idempotency_key) where idempotency_key is not null
      do update set status = 'pending',
                    scheduled_at = excluded.scheduled_at,
                    next_attempt_at = null,
                    attempts = 0,
                    last_error = null,
                    payload = excluded.payload,
                    target_url = excluded.target_url,
                    updated_at = now()
      `,
      [
        tenantId,
        studentUserId,
        lead.rows[0].id,
        JSON.stringify({
          provider: 'generic',
          formName: 'Worker集成测试客资',
          examType: '专升本',
          source: 'integration',
          leadId: lead.rows[0].id,
          student: { id: studentUserId, name: '烟测学生', phone: '13800000000' },
          referral: { refCode: 'WORKER1', referrerUserId: salesUserId, boundAt: lead.rows[0].bound_at },
        }),
        webhook.url,
      ],
    );
    await pool.query('commit');

    await runWorkerOnce();
    assert.equal(webhook.requests.length, 1, 'worker should send exactly one webhook request');
    assert.equal(webhook.requests[0].body.event, 'lead.created', 'generic webhook should send lead.created event');
    assert.equal(webhook.requests[0].body.data.student.phone, '13800000000', 'webhook body should include lead phone');
    assert.ok(!JSON.stringify(webhook.requests[0]).includes('crm-secret-worker-test'), 'webhook request should not leak CRM secret for generic provider');

    const queue = await pool.query(
      `
      select status, attempts, last_http_code, sent_at
      from public.crm_webhook_queue
      where tenant_id = $1 and idempotency_key = 'lead:worker-integration'
      limit 1
      `,
      [tenantId],
    );
    assert.equal(queue.rows[0]?.status, 'sent', 'CRM queue item should be sent');
    assert.equal(queue.rows[0]?.attempts, 1, 'CRM queue item should record one attempt');
    assert.equal(queue.rows[0]?.last_http_code, 200, 'CRM queue item should record HTTP 200');
    assert.ok(queue.rows[0]?.sent_at, 'CRM queue item should record sent_at');

    const logs = await pool.query(
      `
      select outcome, http_code, request_payload, response_summary
      from public.crm_webhook_log
      where tenant_id = $1 and record_id = $2
      order by created_at desc
      limit 1
      `,
      [tenantId, studentUserId],
    );
    assert.equal(logs.rows[0]?.outcome, 'sent', 'CRM log should record sent outcome');
    assert.equal(logs.rows[0]?.http_code, 200, 'CRM log should record HTTP 200');
    assert.equal(logs.rows[0]?.request_payload?.provider, 'generic', 'CRM log should record provider');
    assert.ok(!JSON.stringify(logs.rows[0]).includes('crm-secret-worker-test'), 'CRM log should not leak secret');

    console.log('CRM worker integration test complete.');
  } finally {
    await pool.end();
    await webhook.close();
  }
}

main().catch(error => {
  console.error(error);
  process.exit(1);
});
