import 'dart:async';
import 'dart:convert';
import 'dart:developer';

import 'package:flutter/material.dart';

import '../../api/errors/error_mapper.dart';
import '../../api/marianumcloud/app_password/delete_app_password.dart';
import '../../api/marianumcloud/login_flow/login_flow_api.dart';
import '../../model/account_data.dart';
import '../../routing/app_routes.dart';
import '../../widget/app_progress_indicator.dart';

/// Die beiden Durchläufe des Login Flow v2: Der erste liefert das allgemeine
/// App-Passwort (voller Browser-Login inkl. 2FA), der zweite das
/// Talk-App-Passwort für die zweite Push-Subscription — der Browser hat dann
/// bereits eine Session, es bleibt nur der „Zugriff gewähren"-Tipp.
enum _FlowStep { primary, talk }

/// Runs the Nextcloud Login Flow v2: opens the browser login, polls until the
/// user confirmed it there (2FA happens inside the browser) and adopts the
/// returned app password via [AccountData.setLoginFlow]. A second, skippable
/// pass mints the Talk app password so flow accounts keep BOTH push
/// subscriptions (see PushRegistrationType). Pops `true` once the primary
/// credential was adopted, `false`/`null` when the user backs out before that.
class NextcloudLoginFlowPage extends StatefulWidget {
  const NextcloudLoginFlowPage({super.key});

  @override
  State<NextcloudLoginFlowPage> createState() => _NextcloudLoginFlowPageState();
}

class _NextcloudLoginFlowPageState extends State<NextcloudLoginFlowPage>
    with WidgetsBindingObserver {
  static const _pollInterval = Duration(seconds: 3);
  // Serverseitig verfällt der Flow-Token nach 20 Minuten — danach würde der
  // Poll für immer 404 liefern, also vorher mit klarer Meldung abbrechen.
  static const _flowTimeout = Duration(minutes: 15);

  final LoginFlowApi _api = LoginFlowApi();
  _FlowStep _step = _FlowStep.primary;
  LoginFlowInit? _flow;
  Timer? _timer;
  DateTime? _startedAt;
  bool _polling = false;
  bool _finished = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    unawaited(_start());
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _timer?.cancel();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // Der Nutzer kommt gerade aus dem Browser zurück — sofort pollen statt
    // bis zu einem Intervall zu warten.
    if (state == AppLifecycleState.resumed) unawaited(_poll());
  }

  Future<void> _start() async {
    _timer?.cancel();
    setState(() {
      _error = null;
      _flow = null;
    });
    try {
      final flow = await _api.start();
      if (!mounted) return;
      setState(() => _flow = flow);
      _startedAt = DateTime.now();
      _timer = Timer.periodic(_pollInterval, (_) => _poll());
      unawaited(AppRoutes.openExternalUrl(flow.loginUrl));
    } catch (e) {
      if (!mounted) return;
      setState(() => _error = errorToUserMessage(e));
    }
  }

  Future<void> _poll() async {
    final flow = _flow;
    if (flow == null || _polling || _finished || _error != null) return;
    final startedAt = _startedAt;
    if (startedAt != null &&
        DateTime.now().difference(startedAt) > _flowTimeout) {
      _timer?.cancel();
      setState(
        () => _error =
            'Zeitüberschreitung — die Anmeldung im Browser wurde nicht abgeschlossen.',
      );
      return;
    }
    _polling = true;
    try {
      final credentials = await _api.poll(flow);
      if (credentials == null || _finished || !mounted) return;
      if (!LoginFlowApi.loginNameMatches(
        expected: AccountData().getUsername(),
        actual: credentials.loginName,
      )) {
        _timer?.cancel();
        // Das versehentlich für das fremde Konto ausgestellte App-Passwort
        // nicht liegen lassen.
        unawaited(_revokeForeignAppPassword(credentials));
        setState(
          () => _error =
              'Im Browser wurde ein anderes Konto angemeldet („${credentials.loginName}“). '
              'Bitte versuche es erneut mit deinem Konto.',
        );
        return;
      }
      switch (_step) {
        case _FlowStep.primary:
          await AccountData().setLoginFlow(credentials.appPassword);
          if (!mounted) return;
          // Zweite Freigabe direkt anstoßen: die Browser-Session besteht
          // bereits, es fehlt nur noch der Grant-Tipp.
          setState(() => _step = _FlowStep.talk);
          unawaited(_start());
        case _FlowStep.talk:
          _finished = true;
          _timer?.cancel();
          await AccountData().setAppPasswordTalk(credentials.appPassword);
          if (!mounted) return;
          Navigator.of(context).pop(true);
      }
    } on Object catch (e) {
      // Transienter Poll-Fehler (z.B. kurz offline) — der nächste Tick
      // versucht es erneut.
      log('Login flow poll failed (retrying): $e');
    } finally {
      _polling = false;
    }
  }

  /// Der Talk-Schritt ist optional: ohne zweites App-Passwort funktioniert
  /// alles außer den allgemeinen Nextcloud-Pushes (Talk-Push bleibt erhalten).
  void _skipTalkStep() {
    _finished = true;
    _timer?.cancel();
    Navigator.of(context).pop(true);
  }

  static Future<void> _revokeForeignAppPassword(
    LoginFlowCredentials credentials,
  ) async {
    try {
      final basic = base64Encode(
        utf8.encode('${credentials.loginName}:${credentials.appPassword}'),
      );
      await DeleteAppPassword().run(authorizationHeader: 'Basic $basic');
    } on Object catch (e) {
      log('Login flow: could not revoke foreign app password: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final flow = _flow;
    final error = _error;
    final isTalkStep = _step == _FlowStep.talk;
    // Ab dem Talk-Schritt ist das primäre App-Passwort bereits übernommen —
    // Zurück heißt dann „überspringen" (pop true), nicht „Login abbrechen":
    // die Aufrufer würden bei false den kompletten Login zurückrollen.
    return PopScope(
      canPop: !isTalkStep,
      onPopInvokedWithResult: (didPop, _) {
        if (!didPop && !_finished) _skipTalkStep();
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Nextcloud-Anmeldung')),
        body: SafeArea(
          child: Center(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(24),
              child: ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 420),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Icon(
                      isTalkStep
                          ? Icons.notifications_active_outlined
                          : Icons.verified_user_outlined,
                      size: 56,
                      color: theme.colorScheme.primary,
                    ),
                    const SizedBox(height: 16),
                    Text(
                      isTalkStep
                          ? 'Fast geschafft!'
                          : 'Bestätigung erforderlich',
                      textAlign: TextAlign.center,
                      style: theme.textTheme.titleLarge?.copyWith(
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      isTalkStep
                          ? 'Damit Benachrichtigungen vollständig ankommen, braucht '
                                'die App eine zweite Freigabe. Du bist im Browser '
                                'bereits angemeldet — es genügt ein Tipp auf '
                                '„Zugriff gewähren“.'
                          : 'Dein Konto ist zusätzlich geschützt (z.B. durch '
                                'Zwei-Faktor-Authentifizierung). Schließe die '
                                'Anmeldung im Browser ab — danach geht es hier '
                                'automatisch weiter.',
                      textAlign: TextAlign.center,
                      style: theme.textTheme.bodyMedium?.copyWith(
                        color: theme.colorScheme.onSurfaceVariant,
                      ),
                    ),
                    const SizedBox(height: 24),
                    if (error != null) ...[
                      Text(
                        error,
                        textAlign: TextAlign.center,
                        style: TextStyle(color: theme.colorScheme.error),
                      ),
                      const SizedBox(height: 16),
                      FilledButton(
                        onPressed: _start,
                        child: const Text('Erneut versuchen'),
                      ),
                    ] else if (flow == null) ...[
                      const Center(child: AppProgressIndicator.medium()),
                    ] else ...[
                      FilledButton.icon(
                        icon: const Icon(Icons.open_in_browser),
                        onPressed: () =>
                            unawaited(AppRoutes.openExternalUrl(flow.loginUrl)),
                        label: Text(
                          isTalkStep
                              ? 'Freigabe im Browser bestätigen'
                              : 'Anmeldung im Browser öffnen',
                        ),
                      ),
                      const SizedBox(height: 20),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          const AppProgressIndicator.small(),
                          const SizedBox(width: 10),
                          Text(
                            'Warte auf Bestätigung im Browser…',
                            style: theme.textTheme.bodySmall?.copyWith(
                              color: theme.colorScheme.onSurfaceVariant,
                            ),
                          ),
                        ],
                      ),
                    ],
                    if (isTalkStep) ...[
                      const SizedBox(height: 12),
                      TextButton(
                        onPressed: _skipTalkStep,
                        child: const Text('Überspringen'),
                      ),
                    ],
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
