import 'dart:async';
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:angel3_framework/angel3_framework.dart';
import '../options.dart';
import '../strategy.dart';

/// Determines the validity of an incoming username and password.
// typedef FutureOr<User> LocalAuthVerifier<User>(String? username, String? password);
typedef LocalAuthVerifier<User> = FutureOr<User?> Function(
    String? username, String? password);

class LocalAuthStrategy<User> extends AuthStrategy<User> {
  final _log = Logger('LocalAuthStrategy');

  final RegExp _rgxBasic = RegExp(r'^Basic (.+)$', caseSensitive: false);
  final RegExp _rgxUsrPass = RegExp(r'^([^:]+):(.+)$');

  LocalAuthVerifier<User> verifier;
  String usernameField;
  String passwordField;
  String invalidMessage;
  final bool allowBasic;
  final bool forceBasic;
  String realm;

  LocalAuthStrategy(this.verifier,
      {this.usernameField = 'username',
      this.passwordField = 'password',
      this.invalidMessage = 'Please provide a valid username and password.',
      this.allowBasic = false,
      this.forceBasic = false,
      this.realm = 'Authentication is required.'}) {
    _log.info('Using LocalAuthStrategy');
  }

  @override
  Future<User?> authenticate(RequestContext req, ResponseContext res,
      [AngelAuthOptions? options]) async {
    var localOptions = options ?? AngelAuthOptions();
    User? verificationResult;

    if (allowBasic) {
      var authHeader = req.headers?.value('authorization') ?? '';

      if (_rgxBasic.hasMatch(authHeader)) {
        var base64AuthString = _rgxBasic.firstMatch(authHeader)?.group(1);
        if (base64AuthString == null) {
          return null;
        }
        var authString = String.fromCharCodes(base64.decode(base64AuthString));
        if (_rgxUsrPass.hasMatch(authString)) {
          Match usrPassMatch = _rgxUsrPass.firstMatch(authString)!;
          verificationResult =
              await verifier(usrPassMatch.group(1), usrPassMatch.group(2));
        } else {
          _log.warning('Bad request: $invalidMessage');
          throw ProtevusHttpException.badRequest(errors: [invalidMessage]);
        }

        if (verificationResult == null) {
          res
            ..statusCode = 401
            ..headers['www-authenticate'] = 'Basic realm="$realm"';
          await res.close();
          return null;
        }

        //Allow non-null to pass through
        //return verificationResult;
      }
    } else {
      var body = await req
          .parseBody()
          .then((_) => req.bodyAsMap)
          .catchError((_) => <String, dynamic>{});
      if (_validateString(body[usernameField]?.toString()) &&
          _validateString(body[passwordField]?.toString())) {
        verificationResult = await verifier(
            body[usernameField]?.toString(), body[passwordField]?.toString());
      }
    }

    // User authentication succeeded can return Map(one element), User(non null) or true
    if (verificationResult != null && verificationResult != false) {
      if (verificationResult is Map && verificationResult.isNotEmpty) {
        return verificationResult;
      } else if (verificationResult is! Map) {
        return verificationResult;
      }
    }

    // Force basic if set
    if (forceBasic) {
      //res.headers['www-authenticate'] = 'Basic realm="$realm"';
      res
        ..statusCode = 401
        ..headers['www-authenticate'] = 'Basic realm="$realm"';
      await res.close();
      return null;
    }

    // Redirect failed authentication
    if (localOptions.failureRedirect != null &&
        localOptions.failureRedirect!.isNotEmpty) {
      await res.redirect(localOptions.failureRedirect, code: 401);
      return null;
    }

    _log.info('Not authenticated');
    throw ProtevusHttpException.notAuthenticated();

    /*
    if (verificationResult is Map && verificationResult.isEmpty) {
      if (localOptions.failureRedirect != null &&
          localOptions.failureRedirect!.isNotEmpty) {
        await res.redirect(localOptions.failureRedirect, code: 401);
        return null;
      }

      if (forceBasic) {
        res.headers['www-authenticate'] = 'Basic realm="$realm"';
        return null;
      }

      return null;
    } else if (verificationResult != false ||
        (verificationResult is Map && verificationResult.isNotEmpty)) {
      return verificationResult;
    } else {
      _log.info('Not authenticated');
      throw AngelHttpException.notAuthenticated();
    }
    */
  }

  bool _validateString(String? str) => str != null && str.isNotEmpty;
}