/**
 * CNC 采集服务 Playwright 端到端测试（完整版）
 *
 * 覆盖范围：
 *   套件1: 管理API控制测试（7个）
 *   套件2: 采集数据全链路验证（6个）
 *   套件3: 产量分段跟踪验证（3个）
 *   套件4: 异常处理与恢复验证（3个）
 *   套件5: 心跳上报验证（2个）
 *
 * 前置条件:
 *   1. MariaDB 运行中（cnc_business + cnc_log 库已初始化）
 *   2. CncSimulator 运行在 http://localhost:9001/（网关模式）
 *   3. CncCollector 运行中，管理API在 http://localhost:5800/
 *
 * 安装: npm install @playwright/test mysql2
 * 运行: npx playwright test e2e-collector.spec.ts --reporter=list
 */

import { test, expect, request } from '@playwright/test';
import mysql from 'mysql2/promise';

// ============================================================
// 常量与配置
// ============================================================

const API_BASE = 'http://localhost:5800';
const SIM_GATEWAY = 'http://localhost:9001';
const API_KEY = 'collector_api_key_2026';
const HEADERS = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' };

const DB_BUSINESS = {
  host: 'localhost', user: 'root', password: 'root',
  database: 'cnc_business', charset: 'utf8mb4'
};
const DB_LOG = {
  host: 'localhost', user: 'root', password: 'root',
  database: 'cnc_log', charset: 'utf8mb4'
};

/** 采集地址1对应的模拟端口（启动后动态获取） */
let simulationPort = 0;
/** 原始URL备份（用于恢复） */
let originalUrl = '';
/** 测试开始时间（用于过滤测试产生的数据） */
const testStartTime = new Date();

// ============================================================
// 数据库查询辅助函数
// ============================================================

async function queryBusiness(sql: string, params?: any[]) {
  const conn = await mysql.createConnection(DB_BUSINESS);
  try {
    const [rows] = await conn.query(sql, params);
    return rows as any[];
  } finally {
    await conn.end();
  }
}

async function queryLog(sql: string, params?: any[]) {
  const conn = await mysql.createConnection(DB_LOG);
  try {
    const [rows] = await conn.query(sql, params);
    return rows as any[];
  } finally {
    await conn.end();
  }
}

async function executeBusiness(sql: string, params?: any[]) {
  const conn = await mysql.createConnection(DB_BUSINESS);
  try {
    await conn.execute(sql, params);
  } finally {
    await conn.end();
  }
}

/** 辅助：调用采集服务管理API */
async function collectorApi(method: string, path: string, headers?: Record<string, string>) {
  const ctx = await request.newContext();
  const opts: any = { headers: headers || HEADERS };
  if (method === 'GET') return ctx.get(`${API_BASE}${path}`, opts);
  return ctx.post(`${API_BASE}${path}`, opts);
}

/** 辅助：等待指定毫秒 */
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

// ============================================================
// 全局串行配置
// ============================================================

test.describe.configure({ mode: 'serial' });

// ============================================================
// 全局 Setup / Teardown
// ============================================================

test.beforeAll(async () => {
  console.log('\n===== E2E测试前置准备 =====');

  // 1. 备份并更新采集地址URL（禁用不可达的地址2）
  const addrs = await queryBusiness('SELECT id, url FROM cnc_collect_address WHERE id = 1');
  if (addrs.length > 0) {
    originalUrl = addrs[0].url;
  }
  await executeBusiness('UPDATE cnc_collect_address SET is_enabled = 0 WHERE id = 2');
  console.log('  [1/5] 已禁用不可达地址(id=2)');

  // 2. 通过模拟器管理API启动地址1的模拟
  const startResp = await fetch(`${SIM_GATEWAY}/admin/api/start-address`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ dbAddressId: 1, interval: 5 })
  });
  const startResult = await startResp.json() as any;
  simulationPort = startResult.port;
  console.log(`  [2/5] 模拟器启动地址1模拟 → 端口 ${simulationPort}`);

  // 3. 更新数据库中的采集地址URL
  await executeBusiness(
    'UPDATE cnc_collect_address SET url = ?, is_enabled = 1 WHERE id = 1',
    [`http://localhost:${simulationPort}/`]
  );
  console.log(`  [3/5] 更新采集地址URL → http://localhost:${simulationPort}/`);

  // 4. 停止采集服务（清空状态），刷新配置，再启动
  await collectorApi('POST', '/api/collector/stop');
  await sleep(1000);
  await collectorApi('POST', '/api/collector/refresh');
  await sleep(500);
  await collectorApi('POST', '/api/collector/start');
  console.log('  [4/5] 采集服务已重启并刷新配置');

  // 5. 等待2个采集周期（间隔5秒 × 2 + 余量）
  await sleep(14000);
  console.log('  [5/5] 等待数据采集完成\n');
});

test.afterAll(async () => {
  // 仅在全部测试结束后恢复数据库配置
  // 注意：Playwright的afterAll可能在describe块之间执行
  // 所以这里不做任何清理，由最终测试负责
});

// ============================================================
// 套件1: 采集服务管理API控制测试
// ============================================================

test.describe('套件1: 管理API控制测试', () => {

  test('GET /api/collector/status - 获取服务状态', async () => {
    const resp = await collectorApi('GET', '/api/collector/status');
    expect(resp.status()).toBe(200);
    const body = await resp.json();

    expect(body.code).toBe(0);
    expect(body.data).toBeDefined();
    expect(body.data).toHaveProperty('isRunning');
    expect(body.data).toHaveProperty('startTime');
    expect(body.data).toHaveProperty('uptimeSeconds');
    expect(body.data).toHaveProperty('workerCount');
    expect(body.data).toHaveProperty('workers');
  });

  test('GET /api/collector/status - 无API Key返回401', async () => {
    const ctx = await request.newContext();
    const resp = await ctx.get(`${API_BASE}/api/collector/status`);

    expect(resp.status()).toBe(401);
    const body = await resp.json();
    expect(body.code).toBe(40101);
  });

  test('GET /api/collector/status - 错误API Key返回401', async () => {
    const ctx = await request.newContext();
    const resp = await ctx.get(`${API_BASE}/api/collector/status`, {
      headers: { 'X-API-Key': 'wrong_key' }
    });

    expect(resp.status()).toBe(401);
  });

  test('POST /api/collector/refresh - 刷新配置', async () => {
    const resp = await collectorApi('POST', '/api/collector/refresh');
    expect(resp.status()).toBe(200);
    const body = await resp.json();
    expect(body.code).toBe(0);
    expect(body.data.message).toContain('刷新');
  });

  test('POST /api/collector/stop - 停止采集服务', async () => {
    const resp = await collectorApi('POST', '/api/collector/stop');
    expect(resp.status()).toBe(200);
    const body = await resp.json();
    expect(body.code).toBe(0);

    // 验证状态变为停止
    const statusResp = await collectorApi('GET', '/api/collector/status');
    const statusBody = await statusResp.json();
    expect(statusBody.data.isRunning).toBe(false);
  });

  test('POST /api/collector/start - 启动采集服务', async () => {
    const resp = await collectorApi('POST', '/api/collector/start');
    expect(resp.status()).toBe(200);
    const body = await resp.json();
    expect(body.code).toBe(0);

    await sleep(2000);

    const statusResp = await collectorApi('GET', '/api/collector/status');
    const statusBody = await statusResp.json();
    expect(statusBody.data.isRunning).toBe(true);
  });

  test('未知端点返回404', async () => {
    const resp = await collectorApi('GET', '/api/collector/nonexistent');
    expect(resp.status()).toBe(404);
  });
});

// ============================================================
// 套件2: 采集数据全链路验证
// ============================================================

test.describe('套件2: 采集数据全链路验证', () => {

  test('原始JSON写入日志库 log_collect_raw', async () => {
    const rows = await queryLog(
      'SELECT id, raw_json, is_success, collect_address_id FROM log_collect_raw WHERE created_at >= ? ORDER BY id DESC LIMIT 5',
      [testStartTime]
    );

    expect(rows.length).toBeGreaterThanOrEqual(1);
    const latest = rows[0];

    // 验证raw_json非空且是有效JSON
    expect(latest.raw_json).toBeTruthy();
    const parsed = JSON.parse(latest.raw_json);
    expect(Array.isArray(parsed)).toBe(true);

    // 验证采集成功
    expect(latest.is_success).toBe(1);

    // 验证是地址1的数据
    expect(latest.collect_address_id).toBe(1);
  });

  test('结构化记录写入业务库 cnc_collect_record', async () => {
    const rows = await queryBusiness(
      'SELECT id, machine_id, program_name, part_count, spindle_speed_actual, feed_speed_actual, spindle_load, collect_time FROM cnc_collect_record WHERE created_at >= ? ORDER BY id DESC LIMIT 10',
      [testStartTime]
    );

    expect(rows.length).toBeGreaterThanOrEqual(1);

    // 验证每条记录的关键字段
    for (const r of rows) {
      expect(r.machine_id).toBeGreaterThan(0);
      expect(r.program_name).toBeTruthy();
      expect(r.collect_time).toBeTruthy();
    }
  });

  test('机床实时状态已更新 cnc_machine', async () => {
    const machines = await queryBusiness(
      "SELECT id, last_collect_time, last_program_name, is_online FROM cnc_machine WHERE collect_address_id = 1 AND is_enabled = 1"
    );

    expect(machines.length).toBeGreaterThanOrEqual(1);

    // 至少有一台机床的最后采集时间是测试开始之后
    const recentMachines = machines.filter((m: any) => {
      if (!m.last_collect_time) return false;
      return new Date(m.last_collect_time) >= testStartTime;
    });
    expect(recentMachines.length).toBeGreaterThanOrEqual(1);

    // 至少有一台机床是在线的
    const onlineMachines = machines.filter((m: any) => m.is_online === 1);
    expect(onlineMachines.length).toBeGreaterThanOrEqual(1);
  });

  test('采集地址状态已更新 cnc_collect_address', async () => {
    const addrs = await queryBusiness(
      'SELECT last_collect_time, last_collect_status, fail_count FROM cnc_collect_address WHERE id = 1'
    );

    expect(addrs.length).toBe(1);
    const addr = addrs[0];

    expect(addr.last_collect_status).toBe('success');
    expect(addr.fail_count).toBe(0);
    expect(new Date(addr.last_collect_time) >= testStartTime).toBe(true);
  });

  test('字段映射解析正确性 - 数值在合理范围', async () => {
    const records = await queryBusiness(
      `SELECT program_name, part_count, spindle_speed_set, feed_speed_set,
              spindle_speed_actual, feed_speed_actual, spindle_load, spindle_override,
              power_on_time, run_time, cutting_time, cycle_time
       FROM cnc_collect_record WHERE created_at >= ? ORDER BY id DESC LIMIT 5`,
      [testStartTime]
    );

    expect(records.length).toBeGreaterThanOrEqual(1);

    for (const r of records) {
      // 程序名应非空
      expect(r.program_name).toBeTruthy();

      // 零件数应 >= 0（DECIMAL字段mysql2返回字符串）
      if (r.part_count !== null) {
        expect(Number(r.part_count)).toBeGreaterThanOrEqual(0);
      }

      // 开机时间应 > 0
      if (r.power_on_time !== null) {
        expect(Number(r.power_on_time)).toBeGreaterThan(0);
      }

      // 运行时间应 >= 0
      if (r.run_time !== null) {
        expect(Number(r.run_time)).toBeGreaterThanOrEqual(0);
      }
    }
  });

  test('成功计数递增', async () => {
    const resp1 = await collectorApi('GET', '/api/collector/status');
    const body1 = await resp1.json();
    const success1 = body1.data.totalSuccess;

    // 等待一个采集周期
    await sleep(8000);

    const resp2 = await collectorApi('GET', '/api/collector/status');
    const body2 = await resp2.json();
    const success2 = body2.data.totalSuccess;

    expect(success2).toBeGreaterThanOrEqual(success1);
  });
});

// ============================================================
// 套件3: 产量分段跟踪验证
// ============================================================

test.describe('套件3: 产量分段跟踪验证', () => {

  test('产量段自动创建 - 存在未结账活跃段', async () => {
    const segments = await queryBusiness(
      `SELECT id, machine_id, program_name, start_time, start_part_count, end_part_count
       FROM cnc_production_segment
       WHERE is_settled = 0 AND end_time IS NULL
       ORDER BY id DESC LIMIT 10`
    );

    expect(segments.length).toBeGreaterThanOrEqual(1);

    for (const seg of segments) {
      expect(seg.machine_id).toBeGreaterThan(0);
      expect(seg.program_name).toBeTruthy();
      expect(seg.start_time).toBeTruthy();
    }
  });

  test('段内零件数实时更新 - end_part_count >= start_part_count', async () => {
    const segments = await queryBusiness(
      `SELECT id, start_part_count, end_part_count, program_name
       FROM cnc_production_segment
       WHERE is_settled = 0 AND end_time IS NULL AND end_part_count IS NOT NULL
       LIMIT 10`
    );

    // 活跃段的 end_part_count 应 >= start_part_count（模拟器在持续生产）
    if (segments.length > 0) {
      for (const seg of segments) {
        expect(Number(seg.end_part_count)).toBeGreaterThanOrEqual(Number(seg.start_part_count));
      }
    }
  });

  test('服务停止时活跃段自动结账', async () => {
    // 记录当前活跃段数量
    const beforeSegments = await queryBusiness(
      'SELECT COUNT(*) as cnt FROM cnc_production_segment WHERE is_settled = 0 AND end_time IS NULL'
    );
    const beforeCount = (beforeSegments[0] as any).cnt;

    if (beforeCount === 0) {
      // 没有活跃段，跳过此测试（标记通过）
      return;
    }

    // 停止采集服务
    await collectorApi('POST', '/api/collector/stop');
    await sleep(2000);

    // 验证：所有段都已结账
    const afterSegments = await queryBusiness(
      'SELECT COUNT(*) as cnt FROM cnc_production_segment WHERE is_settled = 0 AND end_time IS NULL'
    );
    const afterCount = (afterSegments[0] as any).cnt;
    expect(afterCount).toBe(0);

    // 验证：结账的段有 close_reason
    const closedSegments = await queryBusiness(
      `SELECT id, close_reason, end_time, quantity
       FROM cnc_production_segment
       WHERE is_settled = 1 AND close_reason IS NOT NULL
       ORDER BY id DESC LIMIT 5`
    );
    expect(closedSegments.length).toBeGreaterThanOrEqual(1);
    for (const seg of closedSegments) {
      expect(seg.close_reason).toBeTruthy();
      expect(seg.end_time).toBeTruthy();
    }

    // 恢复：重新启动采集服务（后续测试可能需要）
    await collectorApi('POST', '/api/collector/start');
    await sleep(5000);
  });
});

// ============================================================
// 套件4: 异常处理与恢复验证
// ============================================================

test.describe('套件4: 异常处理与恢复验证', () => {

  test('模拟器不可达时采集服务优雅处理', async () => {
    // 1. 停止采集服务
    await collectorApi('POST', '/api/collector/stop');
    await sleep(1000);

    // 2. 将地址URL改为不可达地址
    await executeBusiness(
      'UPDATE cnc_collect_address SET url = ? WHERE id = 1',
      ['http://localhost:9999/']
    );

    // 3. 启动采集服务（强制重建worker）
    await collectorApi('POST', '/api/collector/start');

    // 4. 验证：采集服务仍然在运行（没有崩溃）
    const statusResp = await collectorApi('GET', '/api/collector/status');
    const statusBody = await statusResp.json();
    expect(statusBody.data.isRunning).toBe(true);

    // 5. 轮询等待失败记录（重试3次×30秒间隔≈90秒才能完成）
    let isFailed = false;
    for (let i = 0; i < 12; i++) {
      await sleep(10000);
      const addr = await queryBusiness(
        'SELECT last_collect_status, fail_count FROM cnc_collect_address WHERE id = 1'
      );
      const addrData = addr[0] as any;
      isFailed = addrData.last_collect_status === 'fail' || Number(addrData.fail_count) > 0;
      if (isFailed) break;
    }
    expect(isFailed).toBe(true);
  });

  test('模拟器恢复后采集恢复正常', async () => {
    // 1. 停止采集服务
    await collectorApi('POST', '/api/collector/stop');
    await sleep(1000);

    // 2. 恢复采集地址为模拟器真实端口
    await executeBusiness(
      'UPDATE cnc_collect_address SET url = ?, fail_count = 0 WHERE id = 1',
      [`http://localhost:${simulationPort}/`]
    );

    // 3. 启动采集服务（强制重建worker加载正确URL）
    await collectorApi('POST', '/api/collector/start');

    // 4. 等待2个采集周期
    await sleep(14000);

    // 5. 验证：采集地址状态恢复为成功
    const addr = await queryBusiness(
      'SELECT last_collect_status, fail_count FROM cnc_collect_address WHERE id = 1'
    );
    const addrData = addr[0] as any;
    expect(addrData.last_collect_status).toBe('success');

    // 6. 验证：成功计数在增长
    const statusResp = await collectorApi('GET', '/api/collector/status');
    const statusBody = await statusResp.json();
    expect(statusBody.data.totalSuccess).toBeGreaterThan(0);
  });

  test('不可达期间产生失败日志记录', async () => {
    // 查看最近的失败原始记录（由测试17产生）
    const failLogs = await queryLog(
      `SELECT id, is_success, error_message FROM log_collect_raw
       WHERE collect_address_id = 1 AND is_success = 0
       ORDER BY id DESC LIMIT 5`
    );

    // 应该有失败记录
    if (failLogs.length > 0) {
      for (const log of failLogs) {
        expect(log.is_success).toBe(0);
        expect(log.error_message).toBeTruthy();
      }
    }
  });
});

// ============================================================
// 套件5: 心跳上报验证
// ============================================================

test.describe('套件5: 心跳上报验证', () => {

  test('心跳记录定时写入 log_collector_heartbeat', async () => {
    const heartbeats = await queryLog(
      `SELECT id, service_id, status, success_count, fail_count, uptime_seconds, created_at
       FROM log_collector_heartbeat
       WHERE created_at >= ?
       ORDER BY id DESC LIMIT 10`,
      [testStartTime]
    );

    expect(heartbeats.length).toBeGreaterThanOrEqual(1);

    // 验证有心跳状态为 running 的记录
    const runningHeartbeats = heartbeats.filter((h: any) => h.status === 'running');
    expect(runningHeartbeats.length).toBeGreaterThanOrEqual(1);

    // 验证字段完整
    const latest = heartbeats[0] as any;
    expect(latest.service_id).toBeTruthy();
    expect(Number(latest.uptime_seconds)).toBeGreaterThanOrEqual(0);
  });

  test('停止后心跳状态变更', async () => {
    // 停止采集
    await collectorApi('POST', '/api/collector/stop');
    await sleep(3000);

    // 验证有 stopped 心跳记录
    const stoppedHeartbeats = await queryLog(
      `SELECT id, status FROM log_collector_heartbeat
       WHERE status = 'stopped' AND created_at >= ?
       ORDER BY id DESC LIMIT 5`,
      [testStartTime]
    );

    expect(stoppedHeartbeats.length).toBeGreaterThanOrEqual(1);

    // 恢复采集服务
    await collectorApi('POST', '/api/collector/start');
    await sleep(3000);
  });
});
