import 'dart:convert';

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

import '../api/marianumcloud/nextcloud_ocs.dart';

/// Result of a successful Nextcloud push-v2 registration.
class NextcloudPushRegistration {
  /// Per-user server public key (PEM). Forwarded to MarianumConnect as
  /// `userPublicKey` and used device-side to verify push signatures.
  final String publicKey;

  /// Opaque device identifier assigned by Nextcloud.
  final String deviceIdentifier;

  /// Signature over [deviceIdentifier], forwarded as `deviceIdentifierSignature`.
  final String signature;

  /// True when Nextcloud created a new subscription (HTTP 201); false when the
  /// existing one was already up to date (HTTP 200).
  final bool created;

  const NextcloudPushRegistration({
    required this.publicKey,
    required this.deviceIdentifier,
    required this.signature,
    required this.created,
  });
}

/// Thin client for Nextcloud's push-v2 device endpoints under
/// `/ocs/v2.php/apps/notifications/api/v2/push`. Uses the shared OCS headers,
/// which authenticate with the app password once available (the push
/// registration binds to it).
class NextcloudPushApi {
  static const _path = 'apps/notifications/api/v2/push';

  final http.Client _client;

  NextcloudPushApi({http.Client? client}) : _client = client ?? http.Client();

  /// Registers (or refreshes) a device subscription. [devicePublicKeyPem] must
  /// be the 64-column SPKI PEM. [proxyServer] is the MarianumConnect push-proxy
  /// base URL (with trailing slash).
  ///
  /// [authorizationHeader] overrides the Basic auth (e.g. the Talk app
  /// password — NC binds the subscription to the authenticated session token).
  /// [userAgent] overrides the UA; a Talk-pattern UA makes NC classify the
  /// subscription as apptype `talk` and route Talk pushes to it.
  Future<NextcloudPushRegistration> register({
    required String pushTokenHash,
    required String devicePublicKeyPem,
    required String proxyServer,
    String? authorizationHeader,
    String? userAgent,
  }) async {
    final response = await _client.post(
      NextcloudOcs.uri(_path),
      headers: _headers(authorizationHeader, userAgent),
      body: {
        'pushTokenHash': pushTokenHash,
        'devicePublicKey': devicePublicKeyPem,
        'proxyServer': proxyServer,
      },
    );
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw Exception('NC push register HTTP ${response.statusCode}');
    }
    final json = jsonDecode(utf8.decode(response.bodyBytes));
    final data = (json as Map)['ocs']?['data'];
    if (data is! Map) throw Exception('NC push register: malformed response');
    final publicKey = data['publicKey'] as String?;
    final deviceIdentifier = data['deviceIdentifier'] as String?;
    final signature = data['signature'] as String?;
    if (publicKey == null || deviceIdentifier == null || signature == null) {
      throw Exception('NC push register: missing fields');
    }
    return NextcloudPushRegistration(
      publicKey: publicKey,
      deviceIdentifier: deviceIdentifier,
      signature: signature,
      created: response.statusCode == 201,
    );
  }

  /// Unregisters one device subscription — the DELETE is per session token, so
  /// each registration is removed with its own app password via
  /// [authorizationHeader]. Returns true when the server responded 202,
  /// meaning the proxy subscription should also be removed.
  Future<bool> unregister({String? authorizationHeader}) async {
    final response = await _client.delete(
      NextcloudOcs.uri(_path),
      headers: _headers(authorizationHeader, null),
    );
    return response.statusCode == 202;
  }

  Map<String, String> _headers(
    String? authorizationHeader,
    String? userAgent,
  ) => {
    ...NextcloudOcs.headers(),
    'Authorization': ?authorizationHeader,
    'User-Agent': ?userAgent,
  };
}
