import 'dart:async';

import 'package:flutter/material.dart';

import 'package:logging/logging.dart';

import 'package:pshared/models/auth/pending_login.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/service/verification.dart';

import 'package:pweb/models/state/flow_status.dart';


class TwoFactorProvider extends ChangeNotifier {
  static final _logger = Logger('provider.two_factor');
  late AccountProvider _accountProvider;

  TwoFactorProvider();

  FlowStatus _status = FlowStatus.idle;
  String? _errorMessage;
  String? _currentPendingToken;
  Timer? _cooldownTimer;
  int _cooldownRemainingSeconds = 0;
  DateTime? _cooldownUntil;

  FlowStatus get status => _status;
  bool get isSubmitting => _status == FlowStatus.submitting;
  bool get isResending => _status == FlowStatus.resending;
  bool get hasError => _status == FlowStatus.error;
  bool get verificationSuccess => _status == FlowStatus.success;
  String? get errorMessage => _errorMessage;
  int get cooldownRemainingSeconds => _cooldownRemainingSeconds;
  bool get isCooldownActive => _cooldownRemainingSeconds > 0;
  PendingLogin? get pendingLogin => _accountProvider.pendingLogin;

  void update(AccountProvider accountProvider) {
    _accountProvider = accountProvider;
    final pending = accountProvider.pendingLogin;
    final token = pending?.pendingToken.token;
    if (token != _currentPendingToken || accountProvider.account == null) {
      _resetState();
      _currentPendingToken = token;
    }
    _syncCooldown(pending);
  }

  Future<void> submitCode(String code) async {
    _errorMessage = null;
    _setStatus(FlowStatus.submitting);

    try {
      final pending = _accountProvider.pendingLogin;
      if (pending == null) {
        throw Exception('No pending login available');
      }
      final account = await VerificationService.confirmLoginCode(
        pending: pending,
        code: code,
      );
      _accountProvider.completePendingLogin(account);
      _currentPendingToken = null;
      _setStatus(FlowStatus.success);
    } catch (e) {
      _errorMessage = e.toString();
      _logger.warning('Failed to verify code: ${e.toString()}', e);
      _setStatus(FlowStatus.error);
    }
  }

  Future<void> resendCode() async {
    final pending = _accountProvider.pendingLogin;
    if (pending == null) {
      _logger.warning('No pending login to resend code for');
      return;
    }
    if (isResending || isCooldownActive) return;

    _errorMessage = null;
    _setStatus(FlowStatus.resending);

    try {
      final confirmation = await VerificationService.resendLoginCode(pending);
      _accountProvider.updatePendingLogin(confirmation);
      _startCooldown(confirmation.cooldownSeconds);
      _setStatus(FlowStatus.idle);
    } catch (e) {
      _logger.warning('Failed to resend login code', e);
      _errorMessage = e.toString();
      _setStatus(FlowStatus.error);
    }
  }

  void reset() {
    _resetState();
    _currentPendingToken = null;
  }

  void _resetState() {
    _status = FlowStatus.idle;
    _errorMessage = null;
    _stopCooldown();
    notifyListeners();
  }

  void _syncCooldown(PendingLogin? pending) {
    if (pending == null) {
      _stopCooldown(notify: _cooldownRemainingSeconds != 0);
      return;
    }

    final until = pending.cooldownUntil;
    if (until == null) {
      _stopCooldown(notify: _cooldownRemainingSeconds != 0);
      return;
    }

    if (!_isCooldownActive(until) && _cooldownRemainingSeconds != 0) {
      _stopCooldown(notify: true);
      return;
    }

    if (_cooldownUntil == null || _cooldownUntil != until) {
      _startCooldownUntil(until);
    }
  }

  void _startCooldown(int seconds) {
    final until = DateTime.now().add(Duration(seconds: seconds));
    _startCooldownUntil(until);
  }

  void _startCooldownUntil(DateTime until) {
    _cooldownTimer?.cancel();
    _cooldownUntil = until;
    _cooldownRemainingSeconds = _cooldownRemaining();

    if (_cooldownRemainingSeconds <= 0) {
      _cooldownTimer = null;
      _cooldownUntil = null;
      notifyListeners();
      return;
    }

    _cooldownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
      final remaining = _cooldownRemaining();
      if (remaining <= 0) {
        _stopCooldown(notify: true);
        return;
      }
      if (remaining != _cooldownRemainingSeconds) {
        _cooldownRemainingSeconds = remaining;
        notifyListeners();
      }
    });

    notifyListeners();
  }

  bool _isCooldownActive(DateTime until) => until.isAfter(DateTime.now());

  int _cooldownRemaining() {
    final until = _cooldownUntil;
    if (until == null) return 0;
    final remaining = until.difference(DateTime.now()).inSeconds;
    return remaining < 0 ? 0 : remaining;
  }

  void _stopCooldown({bool notify = false}) {
    _cooldownTimer?.cancel();
    _cooldownTimer = null;
    final hadCooldown = _cooldownRemainingSeconds != 0;
    _cooldownRemainingSeconds = 0;
    _cooldownUntil = null;

    if (notify && hadCooldown) {
      notifyListeners();
    }
  }

  void _setStatus(FlowStatus status) {
    _status = status;
    notifyListeners();
  }

  @override
  void dispose() {
    _stopCooldown();
    super.dispose();
  }
}
