// connect_passthrough_test.dart — M6 透传逐字节断言单测
//
// 验证约束（§3.1）：ConnectApi.fetchConfig 返回的字符串，
// 必须与传入 VpnBridge.start 的字符串逐字节相等，Dart 层禁止拼装/修改。
//
// tsk_nuoKSM4Vt-zK
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pangolin_vpn/services/connect_api.dart';
import 'package:pangolin_vpn/services/vpn_bridge.dart';

// 与 mock server 的 connectConfig 结构一致（四块：inbounds/outbounds/route/dns）
const _kMockConfigJson =
    '{"log":{"level":"warn","timestamp":true},'
    '"inbounds":[{"type":"tun","tag":"tun-in","strict_route":true}],'
    '"outbounds":[{"type":"urltest","tag":"auto","outbounds":["reality-out","hy2-out"]}],'
    '"route":{"final":"auto"},'
    '"dns":{"final":"remote"}}';

void main() {
  group('M6 透传约束 — ConnectApi.fetchConfig → VpnBridge.start', () {
    test('fetchConfig 结果与 VpnBridge.start 接收的字符串逐字节相等', () async {
      // ── Arrange ──
      final mockClient = MockClient((request) async {
        // 验证请求格式符合 §3.1
        expect(request.method, equals('POST'));
        expect(request.url.path, contains('/connect'));
        expect(request.headers['Authorization'], startsWith('Bearer '));
        expect(request.headers['Content-Type'], contains('application/json'));
        return http.Response(
          _kMockConfigJson,
          200,
          headers: {'content-type': 'application/json'},
        );
      });

      final api = ConnectApi(
        baseUrl: 'http://mock-host',
        authToken: 'test-token',
        client: mockClient,
      );
      final bridge = VpnBridge();

      // ── Act ──
      final fetchedJson = await api.fetchConfig(
        nodeId: 'sg-1',
        deviceId: 'dev-001',
      );
      await bridge.start(fetchedJson); // 透传，不修改

      // ── Assert：逐字节相等 ──
      expect(
        bridge.lastConfigJson,
        equals(fetchedJson),
        reason: 'VpnBridge.start 接收的字符串必须与 fetchConfig 返回的字符串逐字节一致（§3.1 透传约束）',
      );
      // 双向验证：与原始 HTTP 响应体相等
      expect(
        bridge.lastConfigJson,
        equals(_kMockConfigJson),
        reason: 'VpnBridge.start 接收的字符串必须等于 HTTP 响应体原文',
      );
    });

    test('HTTP 401 → ConnectApiException(statusCode=401)', () async {
      // 指定 charset=utf-8，否则 http.Response 默认用 Latin-1 编码，
      // 遇到中文字符（鉴权失败）会抛 ArgumentError: Contains invalid characters。
      final mockClient = MockClient((_) async => http.Response(
            '{"code":"unauthorized","message_zh":"鉴权失败","message_en":"Unauthorized"}',
            401,
            headers: {'content-type': 'application/json; charset=utf-8'},
          ));
      final api = ConnectApi(
        baseUrl: 'http://mock-host',
        authToken: 'bad-token',
        client: mockClient,
      );

      await expectLater(
        api.fetchConfig(nodeId: 'sg-1', deviceId: 'dev-001'),
        throwsA(
          isA<ConnectApiException>()
              .having((e) => e.statusCode, 'statusCode', 401),
        ),
      );
    });

    test('HTTP 500 → ConnectApiException(statusCode=500)', () async {
      final mockClient = MockClient(
        (_) async => http.Response('Internal Server Error', 500),
      );
      final api = ConnectApi(
        baseUrl: 'http://mock-host',
        authToken: 'token',
        client: mockClient,
      );

      await expectLater(
        api.fetchConfig(nodeId: 'sg-1', deviceId: 'dev-001'),
        throwsA(isA<ConnectApiException>()),
      );
    });

    test('响应体非 JSON → ConnectApiException(statusCode=-2)', () async {
      final mockClient = MockClient(
        (_) async => http.Response('not-valid-json', 200),
      );
      final api = ConnectApi(
        baseUrl: 'http://mock-host',
        authToken: 'token',
        client: mockClient,
      );

      await expectLater(
        api.fetchConfig(nodeId: 'sg-1', deviceId: 'dev-001'),
        throwsA(
          isA<ConnectApiException>()
              .having((e) => e.statusCode, 'statusCode', -2),
        ),
      );
    });

    test('fetchConfig 不修改响应体（额外字段、空格等）', () async {
      // 包含额外空格/换行的 JSON，透传后不应被重新格式化
      const rawWithSpaces =
          '{"log":  {"level":  "warn"},  "inbounds":  [],  "outbounds":  [],  "route":  {},  "dns":  {}}';
      final mockClient = MockClient(
        (_) async => http.Response(rawWithSpaces, 200,
            headers: {'content-type': 'application/json'}),
      );
      final api = ConnectApi(
        baseUrl: 'http://mock-host',
        authToken: 'token',
        client: mockClient,
      );
      final bridge = VpnBridge();

      final fetched = await api.fetchConfig(nodeId: 'x', deviceId: 'y');
      await bridge.start(fetched);

      expect(
        bridge.lastConfigJson,
        equals(rawWithSpaces),
        reason: '原始响应体的所有空格/格式必须原样保留，不得重新序列化',
      );
    });
  });
}
