import "package:flutter/foundation.dart";

import "api.dart";
import "i18n.dart";
import "models.dart";
import "servers.dart";

/// Session state for whichever server is active. The tokens themselves are held
/// per server by [serverRegistry] — each one is a separate PocketBase, so a
/// session cannot be shared — and this service mirrors the active one, so every
/// screen goes on reading `authService.user` without knowing that more than one
/// server exists.
class AuthService extends ChangeNotifier {
  final ApiClient api;
  bool ready = false;

  /// In-memory (never persisted) app-lock flag. When biometric login is enabled
  /// the app starts/returns locked: the token is still valid but the UI hides
  /// behind a biometric unlock instead of jumping straight to the dashboard.
  bool locked = false;

  AuthService(this.api) {
    api.onUnauthorized = _onUnauthorized;
    // Switching servers swaps the whole session: a different PocketBase, a
    // different user record. Everything watching this service rebuilds on it.
    serverRegistry.addListener(notifyListeners);
  }

  /// The user of the active server's session, or null when it has none.
  AuthUser? get user => serverRegistry.activeSession?.user;

  bool get isAuthenticated => serverRegistry.activeToken != null;

  void lock() {
    if (!locked && isAuthenticated) {
      locked = true;
      notifyListeners();
    }
  }

  void unlock() {
    if (locked) {
      locked = false;
      notifyListeners();
    }
  }

  /// Sessions are read by [ServerRegistry.load]; this only flips the flag that
  /// tells the app boot is done.
  Future<void> loadFromStorage() async {
    ready = true;
    notifyListeners();
  }

  /// Logs into one server and makes it active. The login screen calls it for
  /// whichever server is active; the server sheet calls it for one that isn't
  /// yet, which is why the credentials are checked against that server's own
  /// base before anything switches.
  Future<void> connect(String serverId, String email, String password) async {
    final server = serverRegistry.byId(serverId);
    if (server == null) throw ApiException(404, t("servers.unknown"));
    final (token, u) = await api.loginAt(serverRegistry.baseFor(server), email, password);
    locked = false;
    serverRegistry.setSession(serverId, token, u);
    serverRegistry.setActive(serverId);
    notifyListeners();
  }

  Future<void> login(String email, String password) =>
      connect(serverRegistry.activeId, email, password);

  /// Signs out of one server without leaving the app — the server sheet's own
  /// action. Dropping the active one falls back to home, the same way an expired
  /// token does.
  void disconnect(String serverId) {
    serverRegistry.clearSession(serverId);
    if (serverRegistry.activeId == serverId && serverId != kHomeServerId) {
      serverRegistry.setActive(kHomeServerId);
    }
    notifyListeners();
  }

  /// Replaces the cached user of the active server's session — the settings
  /// screen renaming the account, say. The token it was minted with stands.
  void adoptUser(AuthUser updated) {
    final session = serverRegistry.activeSession;
    if (session == null) return;
    serverRegistry.setSession(serverRegistry.activeId, session.token, updated);
    notifyListeners();
  }

  /// Adopts the role from a freshly fetched profile. A session's role can change
  /// under it — creating an organization promotes the creator to that org's admin
  /// — and the nav gates the Users tab on the cached copy, so it has to catch up
  /// without requiring a re-login. A no-op when the role is unchanged.
  Future<void> adoptRole(UserProfile profile) async {
    final u = user;
    if (u == null || profile.role == u.role) return;
    adoptUser(AuthUser(id: u.id, email: u.email, name: u.name, role: profile.role));
  }

  /// Signs out everywhere. Leaving the app means leaving every server you
  /// reached from it — a live token left behind on a shared phone would be worse
  /// than the inconvenience of logging back in.
  Future<void> logout() async {
    locked = false;
    serverRegistry.clearAllSessions();
    serverRegistry.setActive(kHomeServerId);
    notifyListeners();
  }

  /// A server rejected the token it was given. Only that server's session ends:
  /// a remote one timing out shouldn't tip you out of the app, so fall back to
  /// home while it is still connected, and land on the login screen only when
  /// there is nothing left to fall back to.
  void _onUnauthorized(String serverId) {
    serverRegistry.clearSession(serverId);
    if (serverRegistry.activeId == serverId &&
        serverId != kHomeServerId &&
        serverRegistry.isConnected(kHomeServerId)) {
      serverRegistry.setActive(kHomeServerId);
    }
    if (!isAuthenticated) locked = false;
    notifyListeners();
  }
}
