import 'dart:async';

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

import 'package:hl_lieferservice/data/network/auth_session_event.dart';
import 'package:hl_lieferservice/data/network/keycloak_oidc_token_provider.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart';
import 'package:hl_lieferservice/main.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';

/// AuthBloc bridge between der UI und dem [KeycloakOidcTokenProvider].
///
/// Eingehende UI-Events triggern den Provider (Login/Logout/Restore).
/// Provider-Ereignisse kommen über den `events`-Stream rein, werden zu
/// `ProviderSessionChanged` übersetzt und vom Bloc in Zustände überführt.
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc({required this.tokenProvider, required this.operationBloc})
    : super(Unauthenticated()) {
    on<LoginRequested>(_handleLogin);
    on<LogoutRequested>(_handleLogout);
    on<RestoreSessionRequested>(_handleRestore);
    on<ProviderSessionChanged>(_handleProviderEvent);
    // Legacy: ERPframe-Repos feuern bei 401.
    on<SessionExpiredEvent>((event, emit) async {
      await tokenProvider.logout();
      if (locator.isRegistered<Authenticated>()) {
        locator.unregister<Authenticated>();
      }
      emit(Unauthenticated(sessionExpired: true));
    });

    _subscription = tokenProvider.events.listen(
      (event) => add(_toBlocEvent(event)),
    );
  }

  final KeycloakOidcTokenProvider tokenProvider;
  final OperationBloc operationBloc;

  late final StreamSubscription<AuthSessionEvent> _subscription;

  Future<void> _handleLogin(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    try {
      emit(Authenticating());
      await tokenProvider.login();
      // Erfolg landet via Stream-Subscription als
      // ProviderSessionChanged.loggedIn → _handleProviderEvent.
    } catch (err, st) {
      debugPrint('Login fehlgeschlagen: $err\n$st');
      emit(Unauthenticated());
      operationBloc.add(
        FailOperation(
          message: 'Login war nicht erfolgreich. Probieren Sie es erneut.',
        ),
      );
    }
  }

  Future<void> _handleLogout(
    LogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    await tokenProvider.logout();
    // Provider feuert AuthLoggedOut → _handleProviderEvent setzt den State.
  }

  Future<void> _handleRestore(
    RestoreSessionRequested event,
    Emitter<AuthState> emit,
  ) async {
    try {
      await tokenProvider.restoreSession();
      // Erfolg landet via Stream als ProviderSessionChanged.loggedIn.
      // Misserfolg: bleibt bei Unauthenticated — kein Snackbar, das
      // ist der normale Cold-Start-Pfad.
    } catch (err, st) {
      debugPrint('Restore-Session fehlgeschlagen: $err\n$st');
      // State unverändert (Unauthenticated). Kein Snackbar — das
      // wäre für den Cold-Start zu viel Lärm.
    }
  }

  Future<void> _handleProviderEvent(
    ProviderSessionChanged event,
    Emitter<AuthState> emit,
  ) async {
    switch (event.kind) {
      case ProviderEventKind.loggedIn:
        try {
          final state = Authenticated.fromClaims(
            claims: event.claims ?? const <String, dynamic>{},
            accessToken: event.accessToken ?? '',
          );
          if (locator.isRegistered<Authenticated>()) {
            locator.unregister<Authenticated>();
          }
          locator.registerSingleton<Authenticated>(state);
          emit(state);
        } catch (err, st) {
          debugPrint('Konnte Claims nicht in Authenticated-State mappen: $err\n$st');
          emit(Unauthenticated());
          operationBloc.add(
            FailOperation(
              message:
                  'Login-Antwort vom Server unvollständig. Bitte erneut versuchen.',
            ),
          );
        }
      case ProviderEventKind.loggedOut:
        if (locator.isRegistered<Authenticated>()) {
          locator.unregister<Authenticated>();
        }
        emit(Unauthenticated());
      case ProviderEventKind.sessionExpired:
        if (locator.isRegistered<Authenticated>()) {
          locator.unregister<Authenticated>();
        }
        emit(Unauthenticated(sessionExpired: true));
    }
  }

  ProviderSessionChanged _toBlocEvent(AuthSessionEvent event) {
    return switch (event) {
      AuthLoggedIn(:final claims) => ProviderSessionChanged(
        ProviderEventKind.loggedIn,
        claims: claims,
        // Wir würden hier idealerweise den Access-Token mit übergeben.
        // Da der Provider den Token nicht im Stream-Event mitliefert,
        // bauen wir später eine Brücke. Für jetzt: leerer String —
        // Authenticated.sessionId wird mit Phase D ohnehin abgeschafft.
        accessToken: '',
      ),
      AuthLoggedOut() => const ProviderSessionChanged(
        ProviderEventKind.loggedOut,
      ),
      AuthSessionExpired() => const ProviderSessionChanged(
        ProviderEventKind.sessionExpired,
      ),
    };
  }

  @override
  Future<void> close() async {
    await _subscription.cancel();
    return super.close();
  }
}
