import 'dart:async';
import 'dart:io';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/data/services/services.dart';
import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/domain/vo/delivery_address.dart';
import 'package:fcs/helpers/firebase_helper.dart';
import 'package:fcs/pages/main/model/base_model.dart';
import 'package:fcs/pagination/paginator_listener.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as Path;

class PackageModel extends BaseModel {
  final log = Logger('PackageModel');

  PaginatorListener packages;
  PaginatorListener customerPackages;
  PaginatorListener activePackages;

  bool isLoading = false;
  int _menuSelectedIndex = 1;

  set menuSelectedIndex(int index) {
    _menuSelectedIndex = index;

    _loadPackages(_menuSelectedIndex == 2);
    _loadCustomerPackages(_menuSelectedIndex == 2);

    notifyListeners();
  }

  get menuSelectedIndex => _menuSelectedIndex;

  void privilegeChanged() {
    if (user != null) {
      _initData();
    }
  }

  Future<void> _initData() async {
    logout();
    _menuSelectedIndex = 1;
    packages = PaginatorListener<Package>(
        (data, id) => Package.fromMap(data, id), onChange: () {
      notifyListeners();
    }, rowPerLoad: 30, insertNewByListener: true);
    customerPackages = PaginatorListener<Package>(
        (data, id) => Package.fromMap(data, id), onChange: () {
      notifyListeners();
    }, rowPerLoad: 30, insertNewByListener: true);
    activePackages = PaginatorListener<Package>(
        (data, id) => Package.fromMap(data, id), onChange: () {
      notifyListeners();
    }, rowPerLoad: 30, insertNewByListener: true);

    _loadPackages(_menuSelectedIndex == 2);
    _loadCustomerPackages(_menuSelectedIndex == 2);
    _loadActivePackages();
  }

  @override
  logout() async {
    if (customerPackages != null) customerPackages.close();
    if (packages != null) packages.close();
    if (activePackages != null) activePackages.close();
  }

  Future<void> _loadPackages(bool isDelivered) async {
    if (user == null) return;
    if (!((user.hasPackages() || user.hasReceiving() || user.hasProcessing())))
      return;
    String path = "/$packages_collection";

    try {
      Query listenerQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: isDelivered);
      Query pageQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: isDelivered);

      pageQuery = pageQuery.orderBy("update_time", descending: true);
      packages.refresh(listeningQuery: listenerQuery, pageQuery: pageQuery);
    } catch (e) {
      log.warning("Error!! $e");
    }
  }

  Future<void> _loadCustomerPackages(bool isDelivered) async {
    if (user == null) return;
    String path = "/$packages_collection";

    try {
      Query listenerQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: isDelivered)
          .where("user_id", isEqualTo: user.id);
      Query pageQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: isDelivered)
          .where("user_id", isEqualTo: user.id)
          .orderBy("update_time", descending: true);

      customerPackages.refresh(
          listeningQuery: listenerQuery, pageQuery: pageQuery);
    } catch (e) {
      log.warning("Error!! $e");
    }
  }

  Future<void> _loadActivePackages() async {
    if (user == null) return;
    if (!((user.hasPackages() || user.hasReceiving() || user.hasProcessing())))
      return;
    String path = "/$packages_collection";

    try {
      Query listenerQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: false);
      Query pageQuery = Firestore.instance
          .collection(path)
          .where("is_delivered", isEqualTo: false);

      pageQuery = pageQuery.orderBy("update_time", descending: true);
      activePackages.refresh(
          listeningQuery: listenerQuery, pageQuery: pageQuery);
    } catch (e) {
      log.warning("Error!! $e");
    }
  }

  Future<Package> getPackage(String id) async {
    if (user == null) return null;
    String path = "/$packages_collection";
    try {
      DocumentSnapshot snap =
          await Firestore.instance.collection("$path").document(id).get();
      if (snap.exists) {
        var package = Package.fromMap(snap.data, snap.documentID);
        return package;
      }
    } catch (e) {
      log.warning("Error!! $e");
    }
    return null;
  }

  Future<Package> getPackageByTrackingID(String trackingID) async {
    if (user == null) return null;
    String path = "/$packages_collection";
    try {
      var snaps = await Firestore.instance
          .collection("$path")
          .where("tracking_id", isEqualTo: trackingID)
          .getDocuments(source: Source.server);
      if (snaps.documents.length == 1) {
        var snap = snaps.documents[0];
        var package = Package.fromMap(snap.data, snap.documentID);
        return package;
      }
    } catch (e) {
      log.warning("Error!! $e");
    }
    return null;
  }

  Future<Package> lookupPackage(String trackingID) async {
    if (user == null) return null;
    String path = "/$packages_collection";

    try {
      var qsnap = await Firestore.instance
          .collection("$path")
          .where("tracking_id", isEqualTo: trackingID)
          .where("has_user_id", isEqualTo: false)
          .where("is_deleted", isEqualTo: false)
          .getDocuments(source: Source.server);
      if (qsnap.documents.length > 0) {
        var snap = qsnap.documents[0];
        if (snap.exists) {
          var package = Package.fromMap(snap.data, snap.documentID);
          return package;
        }
      }

      qsnap = await Firestore.instance
          .collection("$path")
          .where("tracking_id", isEqualTo: trackingID)
          .where("user_id", isEqualTo: user.id)
          .where("is_deleted", isEqualTo: false)
          .getDocuments(source: Source.server);
      if (qsnap.documents.length > 0) {
        var snap = qsnap.documents[0];
        if (snap.exists) {
          var package = Package.fromMap(snap.data, snap.documentID);
          return package;
        }
      }
    } catch (e) {
      log.warning("Error!! $e");
    }
    return null;
  }

  Future<List<Package>> getPackages(String userID, List<String> status) async {
    List<Package> packages = [];
    try {
      var snaps = await Firestore.instance
          .collection("/$packages_collection")
          .where("status", whereIn: status)
          .where("user_id", isEqualTo: userID)
          .where("is_deleted", isEqualTo: false)
          .getDocuments(source: Source.server);
      packages = snaps.documents.map((documentSnapshot) {
        var p =
            Package.fromMap(documentSnapshot.data, documentSnapshot.documentID);
        return p;
      }).toList();
    } catch (e) {
      log.warning("Error!! $e");
    }
    return packages;
  }

  Future<List<User>> searchUser(String term) {
    return Services.instance.userService.searchUser(term);
  }

  Future<List<Package>> searchPackage(String term) async {
    List<Package> packages =
        await Services.instance.packageService.searchPackage(term);

    Package pkg = await getPackageByTrackingID(term);
    if (pkg != null && !packages.contains(pkg)) {
      packages.insert(0, pkg);
    }
    return packages;
  }

  Future<void> createPackages(User user, List<Package> packages) {
    return Services.instance.packageService
        .createPackages(packages, user.fcsID);
  }

  Future<void> createReceiving(
      User user, Package package, List<File> files) async {
    if (user != null) {
      package.fcsID = user.fcsID;
    }
    if (files != null) {
      if (files.length > uploadPhotoLimit)
        throw Exception("Exceed number of file upload");
      package.photoUrls = package.photoUrls == null ? [] : package.photoUrls;
      String path = Path.join(pkg_files_path);
      List<String> urls = await uploadFiles(path, files);
      urls.forEach((url) {
        package.photoUrls.add(url);
      });
    }
    return Services.instance.packageService.createReceiving(package);
  }

  Future<void> updateReceiving(User user, Package package, List<File> files,
      List<String> deletedUrls) async {
    if (user != null) {
      package.fcsID = user.fcsID;
    }
    if (deletedUrls != null) {
      for (String url in deletedUrls) {
        package.photoUrls.remove(url);
      }
      await deleteStorageFromUrls(deletedUrls);
    }

    if (files != null) {
      var count = (package.photoUrls?.length ?? 0) +
          files.length -
          (deletedUrls?.length ?? 0);

      if (count > uploadPhotoLimit)
        throw Exception("Exceed number of file upload");
      package.photoUrls = package.photoUrls == null ? [] : package.photoUrls;
      String path = Path.join(pkg_files_path);
      List<String> urls = await uploadFiles(path, files);
      urls.forEach((url) {
        package.photoUrls.add(url);
      });
    }
    await Services.instance.packageService.updateReceiving(package);
  }

  Future<void> deleteReceiving(Package package) {
    return Services.instance.packageService.deleteReceiving(package);
  }

  Future<void> updateProcessing(
      Package package, List<File> files, List<String> deletedUrls) async {
    if (deletedUrls != null) {
      for (String url in deletedUrls) {
        package.photoUrls.remove(url);
      }
      await deleteStorageFromUrls(deletedUrls);
    }

    if (files != null) {
      var count = (package.photoUrls?.length ?? 0) +
          files.length -
          (deletedUrls?.length ?? 0);

      if (count > uploadPhotoLimit)
        throw Exception("Exceed number of file upload");
      package.photoUrls = package.photoUrls == null ? [] : package.photoUrls;
      String path = Path.join(pkg_files_path);
      List<String> urls = await uploadFiles(path, files);
      urls.forEach((url) {
        package.photoUrls.add(url);
      });
      package.photoUrls.removeWhere((e) => deletedUrls.contains(e));
    }
    await Services.instance.packageService.updateProcessing(package);
  }

  Future<void> deleteProcessing(Package package) {
    return Services.instance.packageService.deleteProcessing(package);
  }

  Future<void> changeDeliveryAddress(
      Package package, DeliveryAddress deliveryAddress) {
    return Services.instance.packageService
        .changeDeliveryAddress(package.id, deliveryAddress.id);
  }

  Future<void> packageReturn(Package package) {
    return Services.instance.packageService.packageReturn(package.id);
  }
}
