// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// Basic smoke tests for the GeneratedMessage JSON API.
//
// There are more JSON tests in the dart-protoc-plugin package.

import 'dart:convert';

import 'package:fixnum/fixnum.dart' show Int64;
import 'package:test/test.dart';

import 'mock_util.dart' show MockEnum, T;

void main() {
  test('mergeFromProto3Json unknown enum fields with names', () {
    final example = T();

    // No enum value specified.
    expect(example.hasEnm, isFalse);

    // Defaults to first when it doesn't exist.
    expect(example.enm, equals(MockEnum.values.first));
    expect((example..mergeFromProto3Json({'enm': 'a'})).enm.name, equals('a'));

    // Now it's explicitly set after merging.
    expect(example.hasEnm, isTrue);

    expect((example..mergeFromProto3Json({'enm': 'b'})).enm.name, equals('b'));

    // "c" is not a legal enum value.
    expect(
      () => example..mergeFromProto3Json({'enm': 'c'}),
      throwsA(
        allOf(
          isFormatException,
          predicate(
            (FormatException e) => e.message.contains('Unknown enum value'),
          ),
        ),
      ),
    );

    // `example` hasn't changed.
    expect(example.hasEnm, isTrue);
    expect(example.enm.name, equals('b'));

    // "c" is not a legal enum value, but we are ignoring unknown fields, so
    // `enm` value shouldn't change.
    expect(
      (example..mergeFromProto3Json({'enm': 'c'}, ignoreUnknownFields: true))
          .enm
          .name,
      equals('b'),
    );
    expect(example.hasEnm, isTrue);
  });

  test('mergeFromProto3Json unknown enum fields with indices', () {
    // Similar to above, but with indices.
    final example = T();

    expect((example..mergeFromProto3Json({'enm': 2})).enm.name, 'b');
    expect(
      () => example..mergeFromProto3Json({'enm': 3}),
      throwsA(
        allOf(
          isFormatException,
          predicate(
            (FormatException e) => e.message.contains('Unknown enum value'),
          ),
        ),
      ),
    );

    // `example` hasn't changed.
    expect(example.hasEnm, isTrue);
    expect(example.enm.name, equals('b'));

    // "c" is not a legal enum value, but we are ignoring unknown fields, so the
    // value shouldn't change.
    expect(
      (example..mergeFromProto3Json({'enm': 3}, ignoreUnknownFields: true))
          .enm
          .name,
      equals('b'),
    );
    expect(example.hasEnm, isTrue);
  });

  test('testWriteToJson', () {
    final json = makeTestJson().writeToJson();
    checkJsonMap(jsonDecode(json));
  });

  test('testWriteFrozenToJson', () {
    final frozen = makeTestJson()..freeze();
    final json = frozen.writeToJson();
    checkJsonMap(jsonDecode(json));
  });

  test('writeToJsonMap', () {
    final Map m = makeTestJson().writeToJsonMap();
    checkJsonMap(m);
  });

  test('testWriteToJsonMap', () {
    final t = T();
    t.mergeFromJson('''{"1": 123, "2": "hello"}''');
    checkMessage(t);
  });

  test('testMergeFromJsonMap', () {
    final t = T();
    t.mergeFromJsonMap({'1': 123, '2': 'hello'});
    checkMessage(t);
  });

  test('testInt64JsonEncoding', () {
    final value = Int64.parseInt('1234567890123456789');
    final t = T()..int64 = value;
    final encoded = t.writeToJsonMap();
    expect(encoded['5'], '$value');
    final decoded = T()..mergeFromJsonMap(encoded);
    expect(decoded.int64, value);
  });

  test('testFrozentInt64JsonEncoding', () {
    final value = Int64.parseInt('1234567890123456789');
    final frozen =
        T()
          ..int64 = value
          ..freeze();
    final encoded = frozen.writeToJsonMap();
    expect(encoded['5'], '$value');
    final decoded = T()..mergeFromJsonMap(encoded);
    expect(decoded.int64, value);
  });

  test('testJsonMapWithUnknown', () {
    final m = makeTestJson().writeToJsonMap();
    m['9999'] = 'world';
    final t = T()..mergeFromJsonMap(m);
    checkJsonMap(t.writeToJsonMap(), unknownFields: {'9999': 'world'});
  });

  test('testJspbLite2WithUnknown', () {
    final m = makeTestJson().writeToJson();
    final decoded = jsonDecode(m);
    decoded['9999'] = 'world';
    final encoded = jsonEncode(decoded);
    final t = T()..mergeFromJson(encoded);
    checkJsonMap(t.writeToJsonMap(), unknownFields: {'9999': 'world'});
  });

  test('mergeFromJspbLite2 unknown data should be converted to Dart', () {
    // Testing here is a bit indirect (via `toDebugString`) because
    // `unknownJsonData` is not exposed to the users.
    final decoded = {
      '9999': {
        '1': ['a', 'b', 'c'],
      },
    };
    final encoded = jsonEncode(decoded);
    final t = T()..mergeFromJson(encoded);
    // Without converting JS data to Dart we get `[object Object]` here for the
    // field value.
    expect(t.toDebugString(), '{9999: {1: [a, b, c]}}');
  });

  test('writeToJspbLite unknown data should be converted to JS', () {
    final m = makeTestJson().writeToJson();
    final decoded = jsonDecode(m);
    decoded['9999'] = {
      '1': ['a', 'b', 'c'],
    };
    final encoded = jsonEncode(decoded);
    final t = T()..mergeFromJson(encoded);
    // Without converting unknown data (converted to Dart when decoding) to JS,
    // the unknown field values in the output get weird as JS representation of
    // Dart data are serialized directly by the browser's `JSON.stringify`.
    expect(
      t.writeToJson(),
      '{"1":123,"2":"hello","4":[1,2,3],"9999":{"1":["a","b","c"]}}',
    );
  });
}

T makeTestJson() =>
    T()
      ..val = 123
      ..str = 'hello'
      ..int32s.addAll(<int>[1, 2, 3]);

void checkJsonMap(Map m, {Map<String, dynamic>? unknownFields}) {
  expect(m.length, 3 + (unknownFields?.length ?? 0));
  expect(m['1'], 123);
  expect(m['2'], 'hello');
  expect(m['4'], [1, 2, 3]);
  unknownFields?.forEach((k, v) => expect(m[k], v));
}

void checkMessage(T t) {
  expect(t.val, 123);
  expect(t.str, 'hello');
}
