import 'dart:developer';

import 'package:http/http.dart' as http;

import '../../api_params.dart';
import '../../api_response.dart';
import '../../errors/network_exception.dart';
import '../../errors/parse_exception.dart';
import '../../errors/talk_exception.dart';
import '../../http_errors.dart';
import '../nextcloud_ocs.dart';
import 'talk_error.dart';

abstract class TalkApi<T extends ApiResponse?> {
  String path;
  ApiParams? body;
  Map<String, String>? headers;
  Map<String, dynamic>? getParameters;

  TalkApi(this.path, this.body, {this.headers, this.getParameters});

  Future<http.Response>? request(
    Uri uri,
    ApiParams? body,
    Map<String, String>? headers,
  );
  T assemble(String raw);

  Future<T> run() async {
    final endpoint = NextcloudOcs.uri(
      'apps/spreed/api/$path',
      queryParameters: getParameters,
    );
    final mergedHeaders = {...NextcloudOcs.headers(), ...?headers};

    final data = await sendGuarded(
      'Talk $endpoint',
      () => request(endpoint, body, mergedHeaders),
    );
    if (data == null) {
      throw const NetworkException(
        userMessage: 'Keine Antwort vom Talk-Server erhalten.',
        technicalDetails: 'Talk request returned null',
      );
    }

    final status = data.statusCode;
    if (status < 200 || status >= 300) {
      // Talk's OCS errors carry the real reason in the body (expired session,
      // removed participant, ...); include a trimmed preview so the dialog and
      // logs surface the cause instead of just the bare status code.
      final detail = httpErrorDetail('Talk $endpoint', data.body, status);
      log(detail);
      // Talk's own codes need their own wording: a 400/412/413 here is a
      // rejected request, not a struggling server, and "try again later"
      // would send the user in circles. 401/403/404 keep the shared mapping
      // so the auth handling stays in one place.
      if (status == 400 || status == 412 || status == 413 || status == 429) {
        throw TalkException(
          TalkError('failure', status, ''),
          technicalDetails: detail,
        );
      }
      throwForStatus(status, detail);
    }

    try {
      final assembled = assemble(data.body);
      assembled?.headers = data.headers;
      return assembled;
    } catch (e) {
      throw ParseException(technicalDetails: 'Talk $endpoint assemble: $e');
    }
  }
}
