import assert from 'node:assert/strict';
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 MAIN_TENANT_ID = '00000000-0000-0000-0000-000000000001';
const TENANT_CLASS_ID = '00000000-0000-0000-0000-000000000851';
const STUDENT_USER_ID = '00000000-0000-0000-0000-000000000101';

const ids = {
  rule: '00000000-0000-0000-0000-00000000d701',
  disabledRule: '00000000-0000-0000-0000-00000000d702',
};

function runWorkerOnce() {
  const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'student-supervision'], {
    cwd: process.cwd(),
    env: {
      ...process.env,
      DATABASE_URL: databaseUrl,
      WORKER_STUDENT_SUPERVISION_BATCH_SIZE: '10',
      WORKER_STUDENT_SUPERVISION_ID: 'student-supervision-integration-test',
    },
    stdio: ['ignore', 'pipe', 'pipe'],
    windowsHide: true,
  });
  let output = '';
  child.stdout.on('data', chunk => {
    output += chunk.toString();
  });
  child.stderr.on('data', chunk => {
    output += chunk.toString();
  });
  return new Promise((resolve, reject) => {
    child.on('error', reject);
    child.on('exit', code => {
      try {
        assert.equal(code, 0, `worker should exit 0\n${output}`);
        assert.match(output, /student-supervision batch processed=\d+/, 'worker output should include student supervision summary');
        resolve(output);
      } catch (error) {
        reject(error);
      }
    });
  });
}

async function cleanup(pool) {
  await pool.query(
    `
    delete from public.tenant_student_followups
    where tenant_id = $1
      and (
        metadata->'autoSupervision'->>'ruleId' in ($2, $3)
        or metadata->>'ruleId' in ($2, $3)
      )
    `,
    [MAIN_TENANT_ID, ids.rule, ids.disabledRule],
  );
  await pool.query('delete from public.audit_logs where tenant_id = $1 and target_id in ($2, $3)', [MAIN_TENANT_ID, ids.rule, ids.disabledRule]);
  await pool.query('delete from public.tenant_student_supervision_rules where tenant_id = $1 and id in ($2, $3)', [MAIN_TENANT_ID, ids.rule, ids.disabledRule]);
}

async function createRules(pool) {
  await pool.query(
    `
    insert into public.tenant_student_supervision_rules (
      id, tenant_id, name, status, rules, schedule,
      class_id, assigned_to_user_id, limit_count, metadata, next_run_at
    )
    values (
      $1, $2, 'worker 学习督导集成测试', 'active',
      $3::jsonb, $4::jsonb,
      $5, null, 5, '{"source":"student-supervision-worker-test"}'::jsonb,
      now() - interval '1 minute'
    )
    `,
    [
      ids.rule,
      MAIN_TENANT_ID,
      JSON.stringify({
        inactivityDays: 1,
        wrongQuestionThreshold: 1,
        minAnswers: 1,
        lowAccuracyThreshold: 0.99,
        vocabularyDueThreshold: 1,
      }),
      JSON.stringify({
        enabled: true,
        frequency: 'daily',
        hour: 9,
        minute: 0,
        timezone: 'Asia/Shanghai',
      }),
      TENANT_CLASS_ID,
    ],
  );
  await pool.query(
    `
    insert into public.tenant_student_supervision_rules (
      id, tenant_id, name, status, rules, schedule,
      class_id, assigned_to_user_id, limit_count, metadata, next_run_at
    )
    values (
      $1, $2, 'disabled worker 学习督导集成测试', 'disabled',
      $3::jsonb, $4::jsonb,
      $5, null, 5, '{"source":"student-supervision-worker-test"}'::jsonb,
      now() - interval '1 minute'
    )
    `,
    [
      ids.disabledRule,
      MAIN_TENANT_ID,
      JSON.stringify({ inactivityDays: 1 }),
      JSON.stringify({ enabled: true, frequency: 'daily', hour: 9, minute: 0 }),
      TENANT_CLASS_ID,
    ],
  );
}

async function main() {
  const pool = new pg.Pool({ connectionString: databaseUrl });
  try {
    await cleanup(pool);
    await createRules(pool);

    const firstOutput = await runWorkerOnce();
    assert.match(firstOutput, /processed=1/, 'worker should process the active due supervision rule');
    assert.match(firstOutput, /generated=1/, 'worker should generate follow-up tasks for the active rule');
    assert.match(firstOutput, /failed=0/, 'worker should not fail active supervision rule');

    const rule = await pool.query(
      `
      select last_run_at, next_run_at, last_result, metadata
      from public.tenant_student_supervision_rules
      where tenant_id = $1 and id = $2
      `,
      [MAIN_TENANT_ID, ids.rule],
    );
    assert.ok(rule.rows[0]?.last_run_at, 'worker should record last_run_at');
    assert.ok(rule.rows[0]?.next_run_at, 'worker should compute next_run_at');
    assert.equal(rule.rows[0]?.last_result?.status, 'completed', 'worker should record successful last_result');
    assert.equal(rule.rows[0]?.metadata?.studentSupervisionWorker?.lastStatus, 'completed', 'worker metadata should record completion');

    const followups = await pool.query(
      `
      select id, student_user_id, followup_type, status, metadata
      from public.tenant_student_followups
      where tenant_id = $1
        and metadata->'autoSupervision'->>'ruleId' = $2
      `,
      [MAIN_TENANT_ID, ids.rule],
    );
    assert.ok(followups.rowCount >= 1, 'worker should create at least one supervision follow-up');
    assert.ok(
      followups.rows.some(row => row.student_user_id === STUDENT_USER_ID),
      'worker follow-up should include risky smoke student',
    );
    assert.ok(followups.rows.every(row => row.followup_type === 'learning'), 'worker follow-ups should use learning type');
    assert.ok(followups.rows.every(row => row.status === 'open'), 'worker follow-ups should start open');

    const disabled = await pool.query(
      `
      select last_run_at
      from public.tenant_student_supervision_rules
      where tenant_id = $1 and id = $2
      `,
      [MAIN_TENANT_ID, ids.disabledRule],
    );
    assert.equal(disabled.rows[0]?.last_run_at, null, 'worker should not process disabled supervision rules');

    const secondOutput = await runWorkerOnce();
    assert.match(secondOutput, /processed=0/, 'second worker run should not process rule before next_run_at');
    const followupCount = await pool.query(
      `
      select count(*)::integer as count
      from public.tenant_student_followups
      where tenant_id = $1
        and metadata->'autoSupervision'->>'ruleId' = $2
      `,
      [MAIN_TENANT_ID, ids.rule],
    );
    assert.equal(Number(followupCount.rows[0]?.count), followups.rowCount, 'second worker run should not duplicate follow-ups');

    const audit = await pool.query(
      `
      select action, details
      from public.audit_logs
      where tenant_id = $1
        and target_id = $2
        and action = 'tenant.students.supervision_rule_worker_completed'
      order by created_at desc
      limit 1
      `,
      [MAIN_TENANT_ID, ids.rule],
    );
    assert.equal(audit.rows[0]?.details?.workerId, 'student-supervision-integration-test', 'worker audit should record worker id');

    console.log('Student supervision worker integration test complete.');
  } finally {
    await cleanup(pool).catch(() => {});
    await pool.end();
  }
}

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