// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'dart:convert';
import 'dart:typed_data';

import 'package:built_value/serializer.dart';
import 'package:built_value/src/bool_serializer.dart';
import 'package:built_value/src/built_list_serializer.dart';
import 'package:built_value/src/built_map_serializer.dart';
import 'package:built_value/src/built_set_serializer.dart';
import 'package:smithy/smithy.dart' hide Serializer;

/// Built [Serializers] used when running Smithy tests, normalizing all types
/// to JSON which is the format in which the Smithy team defines the test input.
final testSerializers = (Serializers().toBuilder()
      ..addPlugin(SmithyJsonPlugin())
      ..addAll(_testSerializers))
    .build();

final _testSerializers = <Serializer<dynamic>>[
  const _BinaryTestSerializer(),
  const _Int64Serializer(),
  BigIntSerializer.asString,
  const DoubleSerializer(),
  TimestampSerializer.epochSeconds,
  const UnitSerializer(),
  BoolSerializer(),
  BuiltMapSerializer(),
  BuiltListSerializer(),
  BuiltSetSerializer(),
];

/// Builds a [Serializers] object from a base [protocol] serializers, including
/// any test serializers or other [userSerializers] in the final object.
Serializers buildSerializers(
  Serializers protocol,
  List<Serializer<dynamic>>? userSerializers,
) {
  final serializersBuilder = testSerializers.toBuilder()
    ..addAll([
      ...protocol.serializers,
      ..._testSerializers,
      ...?userSerializers,
    ]);
  for (final builderFactory in protocol.builderFactories.entries) {
    serializersBuilder.addBuilderFactory(
      builderFactory.key,
      builderFactory.value,
    );
  }
  return serializersBuilder.build();
}

/// Parameter values that contain binary data MUST be defined using values that
/// can be represented in plain text (for example, use "foo" and not "Zm9vCg==").
/// While this limits the kinds of binary values that can be tested in protocol
/// tests, it allows protocol tests to demonstrate the requirement of many
/// protocols that binary data is automatically base64 encoded and decoded.
class _BinaryTestSerializer implements PrimitiveSerializer<Uint8List> {
  const _BinaryTestSerializer();

  @override
  Uint8List deserialize(
    Serializers serializers,
    Object serialized, {
    FullType specifiedType = FullType.unspecified,
  }) {
    serialized as String;
    return Uint8List.fromList(utf8.encode(serialized));
  }

  @override
  Object serialize(
    Serializers serializers,
    Uint8List object, {
    FullType specifiedType = FullType.unspecified,
  }) {
    throw UnimplementedError();
  }

  @override
  Iterable<Type> get types => [Uint8List, Uint8List(0).runtimeType];

  @override
  String get wireName => 'Blob';
}

class _Int64Serializer implements PrimitiveSerializer<Int64> {
  const _Int64Serializer();

  @override
  Iterable<Type> get types => const [Int64];

  @override
  String get wireName => 'Long';

  @override
  Object serialize(
    Serializers serializers,
    Int64 int64, {
    FullType specifiedType = FullType.unspecified,
  }) {
    return int64.toInt();
  }

  @override
  Int64 deserialize(
    Serializers serializers,
    Object? serialized, {
    FullType specifiedType = FullType.unspecified,
  }) {
    return serialized is num
        ? Int64(serialized.toInt())
        : Int64.parseInt(serialized as String);
  }
}