import 'dart:convert';

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

final class HttpRemoteShareApi implements RemoteShareApi {
  const HttpRemoteShareApi(this.client);

  final HttpApiClient client;

  @override
  Future<RemoteShareSendResult> sendShare({
    required ShareResourceType resourceType,
    required Map<String, Object?> payload,
    required List<String> recipientEmails,
    required String token,
  }) async {
    final response = await client.postJson(
      '/shares',
      bearerToken: token,
      body: {
        'resourceType': _shareResourceTypeToWire(resourceType),
        'payload': payload,
        'recipientEmails': recipientEmails,
      },
      expectedStatuses: const {201},
    );
    return RemoteShareSendResult(
      shareId: _requiredString(response, 'shareId'),
      recipientUserIds: _stringList(response, 'recipientUserIds'),
      unresolvedEmails: _stringList(response, 'unresolvedEmails'),
    );
  }

  @override
  Future<List<ShareInboxItem>> fetchInbox(String token) async {
    final response = await client.getJson('/shares/inbox', bearerToken: token);
    return _list(
      response,
      'items',
    ).map((item) => _inboxItemFromJson(_map(item))).toList();
  }

  @override
  Future<RemoteSyncedItem> acceptShare(String shareId, String token) async {
    final response = await client.postJson(
      '/shares/$shareId/accept',
      bearerToken: token,
    );
    return _syncedItemFromJson(_map(response['createdResource']));
  }

  @override
  Future<void> declineShare(String shareId, String token) {
    return client.postEmpty('/shares/$shareId/decline', bearerToken: token);
  }

  @override
  Future<void> revokeShare(String shareId, String token) {
    return client.postEmpty('/shares/$shareId/revoke', bearerToken: token);
  }
}

ShareInboxItem _inboxItemFromJson(Map<String, Object?> json) {
  return ShareInboxItem(
    shareId: _requiredString(json, 'shareId'),
    senderUserId: _requiredString(json, 'senderUserId'),
    resourceType: _shareResourceTypeFromWire(
      _requiredString(json, 'resourceType'),
    ),
    payloadJson: _jsonObjectString(json['payload']),
    status: _shareInboxStatusFromWire(_requiredString(json, 'status')),
    createdAt: _requiredDateTime(json, 'createdAt'),
    respondedAt: _optionalDateTime(json, 'respondedAt'),
  );
}

RemoteSyncedItem _syncedItemFromJson(Map<String, Object?> json) {
  return RemoteSyncedItem(
    resourceType: _syncResourceTypeFromWire(
      _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 _shareResourceTypeToWire(ShareResourceType type) => switch (type) {
  ShareResourceType.program => 'program',
  ShareResourceType.workoutTemplate => 'workoutTemplate',
};

ShareResourceType _shareResourceTypeFromWire(String value) => switch (value) {
  'program' => ShareResourceType.program,
  'workoutTemplate' => ShareResourceType.workoutTemplate,
  _ => throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    'Unknown share resource type: $value',
  ),
};

ShareInboxStatus _shareInboxStatusFromWire(String value) => switch (value) {
  'pending' => ShareInboxStatus.pending,
  'accepted' => ShareInboxStatus.accepted,
  'declined' => ShareInboxStatus.declined,
  'revoked' => ShareInboxStatus.revoked,
  _ => throw RemoteAuthException(
    RemoteAuthFailure.unknown,
    'Unknown share status: $value',
  ),
};

SyncResourceType _syncResourceTypeFromWire(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',
  ),
};

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

List<String> _stringList(Map<String, Object?> json, String key) {
  return _list(json, key).whereType<String>().toList(growable: false);
}

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 share response.',
  );
}

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 share 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 = json[key];
  return value is String && value.trim().isNotEmpty
      ? DateTime.parse(value).toUtc()
      : null;
}

String _jsonObjectString(Object? value) {
  return jsonEncode(_map(value));
}
