import '../../application/application.dart';
import 'http_api_client.dart';

final class HttpRemoteSyncApi implements RemoteSyncApi {
  const HttpRemoteSyncApi(this.client);

  final HttpApiClient client;

  @override
  Future<RemoteSyncPushResult> push({
    required String deviceId,
    required List<RemoteSyncPushItem> items,
    required String token,
  }) async {
    final response = await client.postJson(
      '/sync/push',
      bearerToken: token,
      body: {
        'deviceId': deviceId,
        'items': items.map(_pushItemToJson).toList(),
      },
    );
    return RemoteSyncPushResult(
      serverCursor: _optionalString(response, 'serverCursor'),
      results: (_list(
        response,
        'results',
      )).map((item) => _pushItemResultFromJson(_map(item))).toList(),
    );
  }

  @override
  Future<RemoteSyncPullResult> pull({
    required String? since,
    required String token,
  }) async {
    final response = await client.getJson(
      '/sync/pull',
      bearerToken: token,
      queryParameters: {'since': since},
    );
    return RemoteSyncPullResult(
      serverCursor: _optionalString(response, 'serverCursor'),
      items: (_list(
        response,
        'items',
      )).map((item) => _syncedItemFromJson(_map(item))).toList(),
    );
  }

  Map<String, Object?> _pushItemToJson(RemoteSyncPushItem item) => {
    'resourceType': _resourceTypeToWire(item.resourceType),
    'clientId': item.clientId,
    'schemaVersion': item.schemaVersion,
    'clientUpdatedAt': item.clientUpdatedAt.toUtc().toIso8601String(),
    'deletedAt': item.deletedAt?.toUtc().toIso8601String(),
    'payload': item.payload,
  };

  RemoteSyncPushItemResult _pushItemResultFromJson(Map<String, Object?> json) {
    return RemoteSyncPushItemResult(
      resourceType: _resourceTypeFromWire(
        _requiredString(json, 'resourceType'),
      ),
      clientId: _requiredString(json, 'clientId'),
      serverId: _optionalString(json, 'serverId'),
      status: _pushStatusFromWire(_requiredString(json, 'status')),
      serverUpdatedAt: _optionalDateTime(json, 'serverUpdatedAt'),
      errorMessage: _optionalString(json, 'message'),
    );
  }

  RemoteSyncedItem _syncedItemFromJson(Map<String, Object?> json) {
    return RemoteSyncedItem(
      resourceType: _resourceTypeFromWire(
        _requiredString(json, 'resourceType'),
      ),
      clientId: _requiredString(json, 'clientId'),
      serverId: _requiredString(json, 'serverId'),
      schemaVersion: _requiredInt(json, 'schemaVersion'),
      clientUpdatedAt: _requiredDateTime(json, 'clientUpdatedAt'),
      serverUpdatedAt: _requiredDateTime(json, 'serverUpdatedAt'),
      deletedAt: _optionalDateTime(json, 'deletedAt'),
      payload: _map(json['payload']),
    );
  }
}

String _resourceTypeToWire(SyncResourceType type) => switch (type) {
  SyncResourceType.exercise => 'exercise',
  SyncResourceType.program => 'program',
  SyncResourceType.workoutTemplate => 'workoutTemplate',
  SyncResourceType.workoutHistory => 'workoutHistory',
  SyncResourceType.mediaAsset => 'mediaAsset',
};

SyncResourceType _resourceTypeFromWire(String value) => switch (value) {
  'exercise' => SyncResourceType.exercise,
  'program' => SyncResourceType.program,
  'workoutTemplate' => SyncResourceType.workoutTemplate,
  'workoutHistory' => SyncResourceType.workoutHistory,
  'mediaAsset' => SyncResourceType.mediaAsset,
  _ => throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    'Unknown sync resource type: $value',
  ),
};

RemoteSyncPushStatus _pushStatusFromWire(String value) => switch (value) {
  'accepted' => RemoteSyncPushStatus.accepted,
  'ignoredOlder' => RemoteSyncPushStatus.ignoredOlder,
  'error' => RemoteSyncPushStatus.error,
  _ => throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    'Unknown sync push status: $value',
  ),
};

List<Object?> _list(Map<String, Object?> json, String key) {
  final value = json[key];
  if (value is List) {
    return value.cast<Object?>();
  }
  return const [];
}

Map<String, Object?> _map(Object? value) {
  if (value is Map) {
    return Map<String, Object?>.from(value);
  }
  throw const RemoteAuthException(
    RemoteAuthFailure.unknown,
    'Expected JSON object.',
  );
}

String _requiredString(Map<String, Object?> json, String key) {
  final value = json[key];
  if (value is String && value.trim().isNotEmpty) {
    return value;
  }
  throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    '$key is missing from sync response.',
  );
}

String? _optionalString(Map<String, Object?> json, String key) {
  final value = json[key];
  return value is String && value.trim().isNotEmpty ? value : null;
}

int _requiredInt(Map<String, Object?> json, String key) {
  final value = json[key];
  if (value is int) {
    return value;
  }
  throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    '$key is missing from sync response.',
  );
}

DateTime _requiredDateTime(Map<String, Object?> json, String key) {
  return DateTime.parse(_requiredString(json, key)).toUtc();
}

DateTime? _optionalDateTime(Map<String, Object?> json, String key) {
  final value = _optionalString(json, key);
  return value == null ? null : DateTime.parse(value).toUtc();
}
