import 'dart:io';

import 'package:app_hit_a_wod/src/models/login_response.model.dart';
import 'package:app_hit_a_wod/src/models/subscription_selection.model.dart';
import 'package:app_hit_a_wod/src/models/token.model.dart';
import 'package:app_hit_a_wod/src/models/user.model.dart';
import 'package:app_hit_a_wod/src/providers/api.provider.dart';
import 'package:app_hit_a_wod/src/providers/user-subscriptions.provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:rxdart/subjects.dart';
import 'package:rxdart/rxdart.dart';

class AuthProvider {

  //keys
  static final _TOKEN_KEY = "haw_token";
  static final _TOKEN_PROTECTED = "haw_token_protected";

  //other providers
  ApiProvider _api;
  UserSubscriptionsProvider _us_provider;
  FlutterSecureStorage _storage;
  LocalAuthentication _local_auth;
  
  //token management
  BehaviorSubject<TokenModel> _tokenSubject;
  Stream<TokenModel> get tokenStream => _tokenSubject.asBroadcastStream();
  TokenModel get currentToken => _tokenSubject.value;

  //user management
  BehaviorSubject<User> _userSubject;
  Stream<User> get userStream => _userSubject.asBroadcastStream();
  User get currentUser => _userSubject.value;

  /**
   * BIOMMETRIC PROTECTION STATUS
   *    --> "L" => Amb seguretat i bloquejat
   *    --> "S" => Amb seguretat i sense bloquejar
   *    --> "C" => Sense seguretat
   */ 
  BehaviorSubject<String> _protectionStatusSubject; 
  Stream<String> get protectionStatusStream => _protectionStatusSubject.stream;
  get currentProtectionStatus => _protectionStatusSubject.value;

  //Constructor + SINGLETON
  static AuthProvider _instancia;
  factory AuthProvider({@required ApiProvider api, @required UserSubscriptionsProvider us}) {
    if(_instancia==null) {
      _instancia = new AuthProvider._internal(api: api, us: us);
    }
    return _instancia;
  }

  AuthProvider._internal({@required ApiProvider api, @required UserSubscriptionsProvider us}) {
    this._api = api;
    this._us_provider = us;

    //init streams
    _storage = new FlutterSecureStorage();
    _local_auth = new LocalAuthentication();
    _tokenSubject = new BehaviorSubject();
    _userSubject = new BehaviorSubject();
    _protectionStatusSubject = new BehaviorSubject();

    this._initTokenAndBiommetricStatus();
  }

  /**
   * AUTH METHODS
   */
  Future<LoginResponse> login(String email, String password, { String custom_user_id }) async {
    try {
      LoginResponse resp = await _api.login(email, password, custom_user_id: custom_user_id);

      if(!isSuperAdmin(resp.subscriptions)) {
        this.setUser(resp.user);
        await this._us_provider.setSubscriptions(resp.subscriptions);
        this.setToken(TokenModel(resp.access_token, fromStorage: false));
      }

      return resp;
    } on Exception catch (e) {
      this.logout();
      throw e;
    }
  }

  Future<LoginResponse> refreshToken() async {
    try {
      var resp = await _api.refreshToken();
      bool setted = await this.setToken(TokenModel(resp.access_token));
      this.setUser(resp.user);
      await this._us_provider.setSubscriptions(resp.subscriptions);
      return resp;
    } on Exception catch(e) {
      this.logout();
      throw e;
    }
  }

  Future<bool> logout() async {
    await this.removeToken();
    this.removeUser();
    await _us_provider.removeSubscriptions();
    return true;
  }

  /**
   * TOKEN MANAGEMENT
   */
  Future<bool> isProtected() async {
    final String is_protected = await _storage.read(key: _TOKEN_PROTECTED);
    return is_protected!=null;
  }

  Future<String> _getTokenFromStorage() async {
    final token = _storage.read(key: _TOKEN_KEY);
    if(token!=null && this.currentProtectionStatus=="L") {
      final bool authenticated = await this._local_auth.authenticateWithBiometrics(
        localizedReason: "Queremos asegurarnos que eres tu",
        stickyAuth: true
      );

      if(authenticated) return token;
      else return null;
    }
    else return token;
  }

  setToken(TokenModel token) async {
    this._api.setToken(token.token);
    await _storage.write(key: _TOKEN_KEY, value: token.token);
    this._tokenSubject.sink.add(token);
    return true;
  }

  protect() async {
    try {
      final bool check = await this._local_auth.authenticateWithBiometrics(
        localizedReason: "Prova"
      );

      if(check) {
        await _storage.write(key: _TOKEN_PROTECTED, value: "Y");
        this._protectionStatusSubject.sink.add("L");
        return true;
      }
      else return false;
    } on Exception catch(e) {
      return false;
    }
  }

  removeProtectionWithCheck() async {
    if(this.currentProtectionStatus!="C") {
      try {
        final bool check = await this._local_auth.authenticateWithBiometrics(
          localizedReason: "Vamos a comprobar que eres tú."
        );
        if(check) {
          await this._removeProtection();
          return true;
        }
        else return false;
      } on Exception {
        return false;
      }
    }
    return false;
  }

  removeToken() async {
    await this._removeProtection();
    _storage.delete(key: _TOKEN_KEY);
    this._tokenSubject.sink.add(null); 
  }

  _removeProtection() async {
    this._protectionStatusSubject.sink.add("S");
    await _storage.delete(key: _TOKEN_PROTECTED);
    return true;
  }

  /**
   * USER MANAGEMENT
   */
  Future<User> updateUser(User user) async {
    User resp = await _api.updateUser(user);
    setUser(resp);
    return resp;
  }

  setUser(User user) {
    this._userSubject.sink.add(user);
  }

  removeUser() {
    this._userSubject.sink.add(null);
  }

  updateUserProfileImage(File img) async {
    int id = currentUser.id;
    if(id==null) throw new Exception("No user or id found");
    User resp = await _api.updateUserProfileImage(id, img);
    setUser(resp);
    return resp;
  }


  /**
   * BIOMMETRICS
   */
  _initTokenAndBiommetricStatus() async {
    //biommetrics
    try {
      final bool canCheckBiometrics = await this._local_auth.canCheckBiometrics;
      if(canCheckBiometrics) {
        this._protectionStatusSubject.sink.add("S");
        if((await _storage.read(key: _TOKEN_PROTECTED))!=null) {
          //is protected
          this._protectionStatusSubject.sink.add("L");
        }
      }
      else {
        this._protectionStatusSubject.sink.add("C");
      }
    } on Exception catch(e) {
      this._protectionStatusSubject.sink.add("C");
    }

    //load token
    this._getTokenFromStorage().then((String token) {
      if(token!=null) {
        this.setToken(TokenModel(token, fromStorage: true));
      }
    });
  }

  static bool isSuperAdmin(List<SubscriptionSelection> subscriptions) {
    bool found = false;
    int i = 0;
    while(!found && i<subscriptions.length) {
      if(subscriptions[i].role.slug=="super_admin") found = true;
      i++;
    }
    return found;
  }

}