import 'dart:convert';

import 'package:shelf/shelf.dart';

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

final class ShareApi {
  const ShareApi({
    required this.createShare,
    required this.listInbox,
    required this.acceptShare,
    required this.declineShare,
    required this.revokeShare,
    required this.authenticateRequest,
  });

  final CreateShareUseCase createShare;
  final ListInboxUseCase listInbox;
  final AcceptShareUseCase acceptShare;
  final DeclineShareUseCase declineShare;
  final RevokeShareUseCase revokeShare;
  final AuthenticateRequestUseCase authenticateRequest;

  Future<Response> create(Request request) async {
    final authenticated = authenticatedRequestFrom(request);
    if (authenticated == null) {
      return _errorResponse(401, 'Unauthorized.');
    }
    try {
      final body = await _readJsonObject(request);
      final result = await createShare.execute(
        senderUserId: authenticated.user.id,
        resourceType: _requiredString(body, 'resourceType'),
        payloadJson: _requiredObject(body, 'payload'),
        recipientEmails: _requiredStringList(body, 'recipientEmails'),
      );
      return _jsonResponse(201, {
        'shareId': result.share.id,
        'recipientUserIds': result.recipientUserIds,
        'unresolvedEmails': result.unresolvedEmails,
      });
    } on ValidationException catch (error) {
      return _errorResponse(400, error.message);
    } on FormatException catch (error) {
      return _errorResponse(400, error.message);
    }
  }

  Future<Response> inbox(Request request) async {
    final authenticated = authenticatedRequestFrom(request);
    if (authenticated == null) {
      return _errorResponse(401, 'Unauthorized.');
    }
    final items = await listInbox.execute(
      recipientUserId: authenticated.user.id,
    );
    return _jsonResponse(200, {
      'items': [
        for (final item in items)
          {
            'shareId': item.share.id,
            'senderUserId': item.share.senderUserId,
            'resourceType': item.share.resourceType.wireName,
            'payload': item.share.payloadJson,
            'status': item.recipient.status.wireName,
            'createdAt': item.share.createdAt.toIso8601String(),
            'respondedAt': item.recipient.respondedAt?.toIso8601String(),
          },
      ],
    });
  }

  Future<Response> accept(Request request, String shareId) async {
    final authenticated = authenticatedRequestFrom(request);
    if (authenticated == null) {
      return _errorResponse(401, 'Unauthorized.');
    }
    try {
      final result = await acceptShare.execute(
        shareId: shareId,
        recipientUserId: authenticated.user.id,
      );
      return _jsonResponse(200, {
        'createdResource': _resourceJson(result.createdResource),
      });
    } on ShareNotFoundException catch (error) {
      return _errorResponse(404, error.message);
    } on ShareConflictException catch (error) {
      return _errorResponse(409, error.message);
    }
  }

  Future<Response> decline(Request request, String shareId) async {
    final authenticated = authenticatedRequestFrom(request);
    if (authenticated == null) {
      return _errorResponse(401, 'Unauthorized.');
    }
    try {
      await declineShare.execute(
        shareId: shareId,
        recipientUserId: authenticated.user.id,
      );
      return Response(204);
    } on ShareNotFoundException catch (error) {
      return _errorResponse(404, error.message);
    } on ShareConflictException catch (error) {
      return _errorResponse(409, error.message);
    }
  }

  Future<Response> revoke(Request request, String shareId) async {
    final authenticated = authenticatedRequestFrom(request);
    if (authenticated == null) {
      return _errorResponse(401, 'Unauthorized.');
    }
    try {
      await revokeShare.execute(
        shareId: shareId,
        senderUserId: authenticated.user.id,
      );
      return Response(204);
    } on ShareNotFoundException catch (error) {
      return _errorResponse(404, error.message);
    }
  }
}

Map<String, Object?> _resourceJson(SyncedResource resource) {
  return {
    'serverId': resource.serverId,
    'clientId': resource.clientId,
    'resourceType': resource.resourceType.wireName,
    'schemaVersion': resource.schemaVersion,
    'clientUpdatedAt': resource.clientUpdatedAt.toIso8601String(),
    'serverUpdatedAt': resource.serverUpdatedAt.toIso8601String(),
    'deletedAt': resource.deletedAt?.toIso8601String(),
    'payload': resource.payloadJson,
  };
}

Future<Map<String, Object?>> _readJsonObject(Request request) async {
  final raw = await request.readAsString();
  final decoded = jsonDecode(raw);
  if (decoded is! Map<String, Object?>) {
    throw const FormatException('Request body must be a JSON object.');
  }
  return decoded;
}

String _requiredString(Map<String, Object?> body, String key) {
  final value = body[key];
  if (value is! String || value.trim().isEmpty) {
    throw FormatException('$key must be a non-empty string.');
  }
  return value;
}

Map<String, Object?> _requiredObject(Map<String, Object?> body, String key) {
  final value = body[key];
  if (value is Map<String, Object?>) {
    return value;
  }
  if (value is Map) {
    return Map<String, Object?>.from(value);
  }
  throw FormatException('$key must be a JSON object.');
}

List<String> _requiredStringList(Map<String, Object?> body, String key) {
  final value = body[key];
  if (value is! List) {
    throw FormatException('$key must be an array.');
  }
  return [
    for (final item in value)
      if (item is String)
        item
      else
        throw FormatException('$key must contain only strings.'),
  ];
}

Response _jsonResponse(int statusCode, Map<String, Object?> body) {
  return Response(
    statusCode,
    body: jsonEncode(body),
    headers: {'content-type': 'application/json'},
  );
}

Response _errorResponse(int statusCode, String message) {
  return _jsonResponse(statusCode, {'error': message});
}
