import 'dart:developer';

import 'package:flutter/foundation.dart';

import '../../api/demo/demo_mode.dart';
import '../../api/errors/auth_exception.dart';
import '../../api/errors/error_mapper.dart';
import '../../api/marianumcloud/app_password/get_app_password.dart';
import '../../api/marianumconnect/auth/device_token_name.dart';
import '../../api/marianumconnect/auth/token_storage.dart';
import '../../api/marianumconnect/queries/auth_login/auth_login.dart';
import '../../api/marianumconnect/queries/auth_logout/auth_logout.dart';
import '../../model/account_data.dart';
import '../../widget_data/widget_sync.dart';

/// Outcome of a login attempt.
enum LoginResult {
  /// Fully logged in — the view transitions to `loggedIn`.
  success,

  /// Credentials rejected or a transport problem; the error is exposed via
  /// [LoginController.errorMessage].
  failure,

  /// MarianumConnect accepted the credentials, but Nextcloud rejects them
  /// (two-factor authentication active or diverging password). The view must
  /// complete the Nextcloud Login Flow v2 in the browser before proceeding.
  nextcloudLoginRequired,
}

/// Owns the login flow's transient state (loading, last error) so it can be
/// driven from a thin Stateful view and unit-tested without a widget tree.
class LoginController extends ChangeNotifier {
  bool _loading = false;
  String? _errorMessage;
  String? _errorDetails;

  bool get loading => _loading;
  String? get errorMessage => _errorMessage;
  String? get errorDetails => _errorDetails;

  Future<LoginResult> submit(String username, String password) async {
    if (_loading) return LoginResult.failure;
    _loading = true;
    _errorMessage = null;
    _errorDetails = null;
    notifyListeners();

    final user = username.trim().toLowerCase();

    // Demo login: the prefix enters local demo mode, password ignored, no
    // network (see DemoMode).
    if (DemoMode.matches(user)) {
      await AccountData().removeData();
      await const MarianumConnectTokenStorage().clear();
      await WidgetSync.clear();
      await WidgetSync.triggerUpdate();
      await AccountData().setDemo(user);
      _loading = false;
      notifyListeners();
      return LoginResult.success;
    }

    try {
      await AccountData().removeData();
      // Vorherigen Token revoken bevor wir einen neuen anfordern — ein altes
      // Account hätte sonst noch einen aktiven Token in api_tokens.
      await const MarianumConnectTokenStorage().clear();
      // Widget-Snapshot löschen, sonst blitzt nach Account-Wechsel kurz der
      // Stundenplan des vorigen Users auf dem Home-Bildschirm.
      await WidgetSync.clear();
      await WidgetSync.triggerUpdate();
      // AuthLogin = Credential-Probe + Token-Create in einem Call.
      // 401 hier heißt: falsches Passwort.
      await AuthLogin().run(
        username: user,
        password: password,
        tokenName: await DeviceTokenName.resolve(),
      );
      await AccountData().setData(user, password);
      // Mint the Nextcloud app password now — it doubles as the Nextcloud
      // credential probe: a rejection means 2FA is active (or the NC password
      // diverges) and the login must finish interactively in the browser.
      final ncReady = await _prepareNextcloudAppPassword();
      _loading = false;
      notifyListeners();
      return ncReady ? LoginResult.success : LoginResult.nextcloudLoginRequired;
    } catch (e) {
      log(e.toString());
      await AccountData().removeData();
      await const MarianumConnectTokenStorage().clear();
      final isWrongCredentials = e is AuthException && e.statusCode == 401;
      _errorMessage = isWrongCredentials
          ? 'Benutzername oder Passwort falsch.'
          : errorToUserMessage(e);
      _errorDetails = errorToTechnicalDetails(e);
      _loading = false;
      notifyListeners();
      return LoginResult.failure;
    }
  }

  /// Tries to mint the Nextcloud app password with the just-verified password.
  /// `false` = Nextcloud rejected the credentials → Login Flow v2 required.
  /// Transport/server problems stay non-blocking (like the previous
  /// fire-and-forget mint): the mint retries with the push registration.
  Future<bool> _prepareNextcloudAppPassword() async {
    try {
      final appPassword = await GetAppPassword().run();
      await AccountData().setAppPassword(appPassword);
      return true;
    } on AuthException {
      return false;
    } on Object catch (e) {
      log('Nextcloud app password mint failed (non-blocking): $e');
      return true;
    }
  }

  /// Rolls the half-finished login back after the user cancelled the
  /// Nextcloud browser login: revoke the fresh MarianumConnect token and wipe
  /// the stored credentials, then surface why the login did not complete.
  Future<void> abortNextcloudLogin() async {
    try {
      await AuthLogout().run();
    } on Object catch (e) {
      log('Login rollback: MC logout failed: $e');
    }
    await AccountData().removeData();
    await const MarianumConnectTokenStorage().clear();
    _errorMessage =
        'Die Anmeldung wurde abgebrochen — dein Konto erfordert die Bestätigung im Browser.';
    _errorDetails = null;
    notifyListeners();
  }
}
