import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';

import '../model/account_data.dart';
import 'push_keypair.dart';
import 'push_registration.dart';
import 'push_registration_store.dart';
import 'push_registration_type.dart';

/// Tri-state result of a single push-chain check.
enum PushCheck { ok, fail, unknown }

/// State of one of the two registrations (general/talk) — Nextcloud binding,
/// proxy binding and last attempt outcome.
@immutable
class PushTypeStatus {
  /// Nextcloud subscription present (device identifier stored).
  final bool nextcloudRegistered;
  final String? registeredNcBaseUrl;
  final String? registeredProxyServer;
  final DateTime? lastRegistrationAt;

  /// Error text of the last registration attempt; null = success or never ran.
  final String? lastRegistrationError;

  const PushTypeStatus({
    required this.nextcloudRegistered,
    required this.registeredNcBaseUrl,
    required this.registeredProxyServer,
    required this.lastRegistrationAt,
    required this.lastRegistrationError,
  });
}

/// Snapshot of every link in the push chain, in delivery order. Pure data —
/// the display rows are derived by [buildPushStatusRows] so the mapping is
/// unit-testable without any plugin.
@immutable
class PushStatusReport {
  final bool settingEnabled;
  final PushCheck osPermission;

  /// Backend capability `pushNotifications`; [PushCheck.unknown] while the
  /// capabilities have not been loaded yet this session.
  final PushCheck serverCapability;
  final bool appPasswordPresent;
  final bool talkAppPasswordPresent;
  final bool keypairPresent;

  /// State of the general (apptype unknown) registration.
  final PushTypeStatus general;

  /// State of the Talk-classified registration.
  final PushTypeStatus talk;

  final String? currentProxyServer;

  const PushStatusReport({
    required this.settingEnabled,
    required this.osPermission,
    required this.serverCapability,
    required this.appPasswordPresent,
    required this.talkAppPasswordPresent,
    required this.keypairPresent,
    required this.general,
    required this.talk,
    required this.currentProxyServer,
  });

  /// True when the stored proxy binding of [status] diverges from the active
  /// endpoint.
  bool proxyEndpointMismatch(PushTypeStatus status) =>
      currentProxyServer != null &&
      PushRegistration.endpointChanged(
        registered: status.registeredProxyServer,
        current: currentProxyServer!,
      );

  /// True when a test notification can actually be delivered. The test push
  /// is a Connect direct push routed via the general registration, so a
  /// healthy general chain suffices — a broken talk registration only affects
  /// Talk message delivery. The OS permission must not be denied (`unknown`
  /// stays permissive, mirroring [PushRegistration.isPermissionUsable]).
  bool get readyForTestNotification =>
      osPermission != PushCheck.fail &&
      general.nextcloudRegistered &&
      (general.registeredProxyServer?.isNotEmpty ?? false) &&
      !proxyEndpointMismatch(general) &&
      general.lastRegistrationError == null;

  /// True when no link in the chain is currently broken. Unknown links stay
  /// permissive (mirroring [readyForTestNotification]) so a not-yet-loaded
  /// capability or an undetermined OS permission does not flip the at-a-glance
  /// health icon to red. Drives the compact status indicator in the settings.
  bool get chainHealthy =>
      buildPushStatusRows(this).every((row) => row.state != PushCheck.fail);
}

/// Collects the current push chain state. Settings/capability flags come from
/// the caller (they live in cubits); everything else is read from the secure
/// stores and the messaging plugin.
Future<PushStatusReport> collectPushStatus({
  required bool settingEnabled,
  required bool capabilityPush,
  required bool capabilitiesLoaded,
  PushRegistrationStore store = const PushRegistrationStore(),
  PushKeypair keypair = const PushKeypair(),
}) async {
  final registration = PushRegistration();

  String? currentProxy;
  try {
    currentProxy = registration.currentProxyServer;
  } on Object {
    currentProxy = null;
  }

  Future<PushTypeStatus> typeStatus(PushRegistrationType type) async =>
      PushTypeStatus(
        nextcloudRegistered:
            (await store.deviceIdentifier(type))?.isNotEmpty ?? false,
        registeredNcBaseUrl: await store.registeredNcBaseUrl(type),
        registeredProxyServer: await store.registeredProxyServer(type),
        lastRegistrationAt: await store.lastRegistrationAt(type),
        lastRegistrationError: await store.lastRegistrationError(type),
      );

  return PushStatusReport(
    settingEnabled: settingEnabled,
    osPermission: await _osPermission(),
    serverCapability: !capabilitiesLoaded
        ? PushCheck.unknown
        : (capabilityPush ? PushCheck.ok : PushCheck.fail),
    appPasswordPresent: AccountData().hasAppPassword(),
    talkAppPasswordPresent: AccountData().hasAppPasswordTalk(),
    keypairPresent: (await keypair.loadPublicKeyPem())?.isNotEmpty ?? false,
    general: await typeStatus(PushRegistrationType.general),
    talk: await typeStatus(PushRegistrationType.talk),
    currentProxyServer: currentProxy,
  );
}

Future<PushCheck> _osPermission() async {
  try {
    final settings = await FirebaseMessaging.instance.getNotificationSettings();
    switch (settings.authorizationStatus) {
      case AuthorizationStatus.authorized:
      case AuthorizationStatus.provisional:
        return PushCheck.ok;
      case AuthorizationStatus.denied:
        return PushCheck.fail;
      case AuthorizationStatus.notDetermined:
        return PushCheck.unknown;
    }
  } on Object {
    return PushCheck.unknown;
  }
}

/// One line in the status checklist.
@immutable
class PushStatusRow {
  final String label;
  final PushCheck state;

  /// Short explanation shown as subtitle — set for failures (what to do) and
  /// for informational details (e.g. the registered URL).
  final String? detail;

  /// When true, the row offers a shortcut into the OS notification settings —
  /// the only place the user can (re)grant the permission after a denial.
  final bool opensNotificationSettings;

  const PushStatusRow({
    required this.label,
    required this.state,
    this.detail,
    this.opensNotificationSettings = false,
  });
}

const _pendingDetail =
    'Registrierung ausstehend — sie wird beim nächsten App-Start '
    'automatisch wiederholt';

/// Derives the display checklist from a [PushStatusReport]. Pure — the order
/// mirrors the actual chain: setting → OS → server → credentials → keys →
/// Nextcloud (general/talk) → Connect (general/talk).
List<PushStatusRow> buildPushStatusRows(PushStatusReport r) => [
  PushStatusRow(
    label: 'Push-Benachrichtigungen aktiviert',
    state: r.settingEnabled ? PushCheck.ok : PushCheck.fail,
    detail: r.settingEnabled
        ? null
        : 'In den Einstellungen deaktiviert — über den Schalter oben aktivieren',
  ),
  PushStatusRow(
    label: 'Benachrichtigungsberechtigung',
    state: r.osPermission,
    opensNotificationSettings: true,
    detail: switch (r.osPermission) {
      PushCheck.ok => null,
      PushCheck.fail =>
        'Die Benachrichtigungsberechtigung wurde in den Systemeinstellungen '
            'deaktiviert',
      PushCheck.unknown => 'Noch nicht erteilt',
    },
  ),
  PushStatusRow(
    label: 'Server-Unterstützung',
    state: r.serverCapability,
    detail: switch (r.serverCapability) {
      PushCheck.ok => null,
      PushCheck.fail =>
        'Der Server unterstützt Push-Benachrichtigungen derzeit nicht',
      PushCheck.unknown => 'Serverinformationen noch nicht geladen',
    },
  ),
  PushStatusRow(
    label: 'App-Passwörter',
    state: r.appPasswordPresent && r.talkAppPasswordPresent
        ? PushCheck.ok
        : PushCheck.fail,
    detail: r.appPasswordPresent && r.talkAppPasswordPresent
        ? null
        : '${_missingPasswords(r)} — wird beim nächsten App-Start '
              'automatisch angefordert',
  ),
  PushStatusRow(
    label: 'Geräteschlüssel',
    state: r.keypairPresent ? PushCheck.ok : PushCheck.fail,
    detail: r.keypairPresent
        ? null
        : 'Nicht vorhanden — wird bei der nächsten Registrierung erzeugt',
  ),
  _nextcloudRow(r.general, 'Nextcloud-Registrierung (Allgemein)'),
  _nextcloudRow(r.talk, 'Nextcloud-Registrierung (Talk)'),
  _connectRow(r, r.general, 'Connect-Registrierung (Allgemein)'),
  _connectRow(r, r.talk, 'Connect-Registrierung (Talk)'),
];

String _missingPasswords(PushStatusReport r) {
  if (!r.appPasswordPresent && !r.talkAppPasswordPresent) {
    return 'Beide fehlen';
  }
  return r.appPasswordPresent
      ? 'Talk-App-Passwort fehlt'
      : 'Allgemeines App-Passwort fehlt';
}

PushStatusRow _nextcloudRow(PushTypeStatus status, String label) =>
    PushStatusRow(
      label: label,
      state: status.nextcloudRegistered ? PushCheck.ok : PushCheck.fail,
      detail: status.nextcloudRegistered
          ? status.registeredNcBaseUrl
          : _pendingDetail,
    );

PushStatusRow _connectRow(
  PushStatusReport r,
  PushTypeStatus status,
  String label,
) {
  final registeredProxy = status.registeredProxyServer;
  if (registeredProxy == null || registeredProxy.isEmpty) {
    return PushStatusRow(
      label: label,
      state: PushCheck.fail,
      detail: _pendingDetail,
    );
  }
  if (r.proxyEndpointMismatch(status)) {
    return PushStatusRow(
      label: label,
      state: PushCheck.fail,
      detail:
          'Für $registeredProxy registriert — der aktive Server ist '
          '${r.currentProxyServer}. Eine erneute Registrierung ist '
          'erforderlich.',
    );
  }
  if (status.lastRegistrationError != null) {
    return PushStatusRow(
      label: label,
      state: PushCheck.fail,
      detail: 'Die letzte Registrierung ist fehlgeschlagen — Details unten',
    );
  }
  return PushStatusRow(
    label: label,
    state: PushCheck.ok,
    detail: registeredProxy,
  );
}
