import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:flutter/foundation.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/account_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
import 'package:invoiceninja_flutter/redux/client/client_selectors.dart';
import 'package:invoiceninja_flutter/redux/client/client_state.dart';
import 'package:invoiceninja_flutter/redux/company/company_state.dart';
import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_selectors.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_selectors.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_selectors.dart';
import 'package:invoiceninja_flutter/redux/design/design_state.dart';
import 'package:invoiceninja_flutter/redux/document/document_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
import 'package:invoiceninja_flutter/redux/group/group_selectors.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
import 'package:invoiceninja_flutter/redux/product/product_selectors.dart';
import 'package:invoiceninja_flutter/redux/product/product_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_selectors.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
import 'package:invoiceninja_flutter/redux/static/static_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_selectors.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_selectors.dart';
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/redux/ui/pref_state.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_selectors.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/edit/company_gateway_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/credit/edit/credit_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart';

// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_state.dart';
import 'package:invoiceninja_flutter/ui/payment_term/edit/payment_term_edit_vm.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_selectors.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_state.dart';
import 'package:invoiceninja_flutter/redux/user/user_state.dart';
import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_state.dart';
import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_state.dart';
import 'package:invoiceninja_flutter/redux/group/group_state.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart';

part 'app_state.g.dart';

abstract class AppState implements Built<AppState, AppStateBuilder> {
  factory AppState({
    @required PrefState prefState,
    String currentRoute,
    String url,
  }) {
    return _$AppState._(
      isLoading: false,
      isSaving: false,
      isTesting: false,
      lastError: '',
      authState: AuthState(url: url),
      staticState: StaticState(),
      userCompanyStates: BuiltList(
          List<int>.generate(kMaxNumberOfCompanies, (i) => i + 1)
              .map((index) => UserCompanyState())
              .toList()),
      uiState: UIState(currentRoute: currentRoute),
      prefState: prefState ?? PrefState(),
    );
  }

  AppState._();

  @override
  @memoized
  int get hashCode;

  bool get isLoading;

  bool get isSaving;

  bool get isTesting;

  String get lastError;

  AuthState get authState;

  StaticState get staticState;

  PrefState get prefState;

  UIState get uiState;

  BuiltList<UserCompanyState> get userCompanyStates;

  //factory AppState([void updates(AppStateBuilder b)]) = _$AppState;
  static Serializer<AppState> get serializer => _$appStateSerializer;

  UserCompanyState get userCompanyState =>
      userCompanyStates[uiState.selectedCompanyIndex];

  bool get isLoaded {
    return productState.isLoaded && clientState.isLoaded;
  }

  AccountEntity get account => userCompany.account;

  CompanyEntity get company => userCompanyState.company;

  List<CompanyEntity> get companies {
    final List<CompanyEntity> list = [];

    for (var companyState in userCompanyStates) {
      if (companyState.company != null) {
        list.add(companyState.company);
      }
    }

    return list
        .where((CompanyEntity company) => (company.id ?? '').isNotEmpty)
        .toList();
  }

  DashboardUIState get dashboardUIState => uiState.dashboardUIState;

  UserEntity get user => userCompanyState.user;

  UserCompanyEntity get userCompany => userCompanyState.userCompany;

  Credentials get credentials =>
      Credentials(token: userCompanyState.token.token, url: authState.url);

  String get accentColor =>
      user?.userCompany?.settings?.accentColor ?? kDefaultAccentColor;

  String get appVersion =>
      '${account.currentVersion}-${kAppVersion.split('.').last}';

  List<HistoryRecord> get historyList =>
      prefState.companyPrefs[uiState.selectedCompanyIndex].historyList
          .where((history) {
        final entityMap = getEntityMap(history.entityType);
        if (entityMap != null) {
          final entity = entityMap[history.id] as BaseEntity;
          if (entity?.isDeleted == true) {
            return false;
          }
        }
        return true;
      }).toList();

  bool shouldSelectEntity({EntityType entityType, List<String> entityList}) {
    final entityUIState = getUIState(entityType);

    if (prefState.isMobile || uiState.isEditing || entityType.isSetting) {
      return false;
    }

    if (entityList.isEmpty) {
      return (entityUIState.selectedId ?? '').isNotEmpty;
    } else if ((entityUIState.selectedId ?? '').isEmpty ||
        !entityList.contains(entityUIState.selectedId)) {
      return true;
    }

    return false;
  }

  BuiltMap<String, SelectableEntity> getEntityMap(EntityType type) {
    switch (type) {
      case EntityType.product:
        return productState.map;
      case EntityType.client:
        return clientState.map;
      case EntityType.invoice:
        return invoiceState.map;
      // STARTER: states switch map - do not remove comment
      case EntityType.paymentTerm:
        return paymentTermState.map;

      case EntityType.design:
        return designState.map;

      case EntityType.credit:
        return creditState.map;

      case EntityType.user:
        return userState.map;
      case EntityType.taxRate:
        return taxRateState.map;
      case EntityType.companyGateway:
        return companyGatewayState.map;
      case EntityType.group:
        return groupState.map;
      case EntityType.document:
        return documentState.map;
      case EntityType.expense:
        return expenseState.map;
      case EntityType.vendor:
        return vendorState.map;
      case EntityType.task:
        return taskState.map;
      case EntityType.project:
        return projectState.map;
      case EntityType.payment:
        return paymentState.map;
      case EntityType.quote:
        return quoteState.map;
      case EntityType.expenseCategory:
        // TODO move to expenseCategoryState.map
        return company.expenseCategoryMap;
      case EntityType.paymentType:
        return staticState.paymentTypeMap;
      case EntityType.currency:
        return staticState.currencyMap;
      case EntityType.country:
        return staticState.countryMap;
      case EntityType.language:
        return staticState.languageMap;
      case EntityType.industry:
        return staticState.industryMap;
      case EntityType.size:
        return staticState.sizeMap;
      case EntityType.gateway:
        return staticState.gatewayMap;
      case EntityType.dateFormat:
        return staticState.dateFormatMap;
      case EntityType.timezone:
        return staticState.timezoneMap;
      default:
        return null;
    }
  }

  BuiltList<String> getEntityList(EntityType type) {
    switch (type) {
      case EntityType.product:
        return productState.list;
      case EntityType.client:
        return clientState.list;
      case EntityType.invoice:
        return invoiceState.list;
      // STARTER: states switch list - do not remove comment
      case EntityType.paymentTerm:
        return paymentTermState.list;

      case EntityType.design:
        return designState.list;

      case EntityType.credit:
        return creditState.list;

      case EntityType.user:
        return userState.list;
      case EntityType.taxRate:
        return taxRateState.list;
      case EntityType.companyGateway:
        return companyGatewayState.list;
      case EntityType.group:
        return groupState.list;
      case EntityType.document:
        return documentState.list;
      case EntityType.expense:
        return expenseState.list;
      case EntityType.vendor:
        return vendorState.list;
      case EntityType.task:
        return taskState.list;
      case EntityType.project:
        return projectState.list;
      case EntityType.payment:
        return paymentState.list;
      case EntityType.quote:
        return quoteState.list;
      default:
        return null;
    }
  }

  EntityUIState getUIState(EntityType type) {
    switch (type) {
      case EntityType.product:
        return productUIState;
      case EntityType.client:
        return clientUIState;
      case EntityType.invoice:
        return invoiceUIState;
      // STARTER: states switch - do not remove comment
      case EntityType.paymentTerm:
        return paymentTermUIState;

      case EntityType.design:
        return designUIState;
      case EntityType.credit:
        return creditUIState;
      case EntityType.user:
        return userUIState;
      case EntityType.taxRate:
        return taxRateUIState;
      case EntityType.companyGateway:
        return companyGatewayUIState;
      case EntityType.group:
        return groupUIState;
      case EntityType.document:
        return documentUIState;
      case EntityType.expense:
        return expenseUIState;
      case EntityType.vendor:
        return vendorUIState;
      case EntityType.task:
        return taskUIState;
      case EntityType.project:
        return projectUIState;
      case EntityType.payment:
        return paymentUIState;
      case EntityType.quote:
        return quoteUIState;
      default:
        return null;
    }
  }

  ListUIState getListState(EntityType type) {
    return getUIState(type).listUIState;
  }

  ProductState get productState => userCompanyState.productState;

  ProductUIState get productUIState => uiState.productUIState;

  ListUIState get productListState => uiState.productUIState.listUIState;

  ClientState get clientState => userCompanyState.clientState;

  ClientUIState get clientUIState => uiState.clientUIState;

  ListUIState get clientListState => uiState.clientUIState.listUIState;

  InvoiceState get invoiceState => userCompanyState.invoiceState;

  InvoiceUIState get invoiceUIState => uiState.invoiceUIState;

  ListUIState get invoiceListState => uiState.invoiceUIState.listUIState;

  // STARTER: state getters - do not remove comment
  PaymentTermState get paymentTermState => userCompanyState.paymentTermState;

  ListUIState get paymentTermListState =>
      uiState.paymentTermUIState.listUIState;

  PaymentTermUIState get paymentTermUIState => uiState.paymentTermUIState;

  DesignState get designState => userCompanyState.designState;

  ListUIState get designListState => uiState.designUIState.listUIState;

  DesignUIState get designUIState => uiState.designUIState;

  CreditState get creditState => userCompanyState.creditState;

  ListUIState get creditListState => uiState.creditUIState.listUIState;

  CreditUIState get creditUIState => uiState.creditUIState;

  UserState get userState => userCompanyState.userState;

  ListUIState get userListState => uiState.userUIState.listUIState;

  UserUIState get userUIState => uiState.userUIState;

  TaxRateState get taxRateState => userCompanyState.taxRateState;

  ListUIState get taxRateListState => uiState.taxRateUIState.listUIState;

  TaxRateUIState get taxRateUIState => uiState.taxRateUIState;

  CompanyGatewayState get companyGatewayState =>
      userCompanyState.companyGatewayState;

  ListUIState get companyGatewayListState =>
      uiState.companyGatewayUIState.listUIState;

  CompanyGatewayUIState get companyGatewayUIState =>
      uiState.companyGatewayUIState;

  GroupState get groupState => userCompanyState.groupState;

  ListUIState get groupListState => uiState.groupUIState.listUIState;

  GroupUIState get groupUIState => uiState.groupUIState;

  DocumentState get documentState => userCompanyState.documentState;

  ListUIState get documentListState => uiState.documentUIState.listUIState;

  DocumentUIState get documentUIState => uiState.documentUIState;

  ExpenseState get expenseState => userCompanyState.expenseState;

  ListUIState get expenseListState => uiState.expenseUIState.listUIState;

  ExpenseUIState get expenseUIState => uiState.expenseUIState;

  VendorState get vendorState => userCompanyState.vendorState;

  ListUIState get vendorListState => uiState.vendorUIState.listUIState;

  VendorUIState get vendorUIState => uiState.vendorUIState;

  TaskState get taskState => userCompanyState.taskState;

  ListUIState get taskListState => uiState.taskUIState.listUIState;

  TaskUIState get taskUIState => uiState.taskUIState;

  ProjectState get projectState => userCompanyState.projectState;

  ListUIState get projectListState => uiState.projectUIState.listUIState;

  ProjectUIState get projectUIState => uiState.projectUIState;

  PaymentState get paymentState => userCompanyState.paymentState;

  ListUIState get paymentListState => uiState.paymentUIState.listUIState;

  PaymentUIState get paymentUIState => uiState.paymentUIState;

  QuoteState get quoteState => userCompanyState.quoteState;

  ListUIState get quoteListState => uiState.quoteUIState.listUIState;

  QuoteUIState get quoteUIState => uiState.quoteUIState;

  SettingsUIState get settingsUIState => uiState.settingsUIState;

  bool hasChanges() {
    switch (uiState.currentRoute) {
      case ClientEditScreen.route:
        return hasClientChanges(clientUIState.editing, clientState.map);
      case ProductEditScreen.route:
        return hasProductChanges(productUIState.editing, productState.map);
      case InvoiceEditScreen.route:
        return hasInvoiceChanges(invoiceUIState.editing, invoiceState.map);
      case PaymentEditScreen.route:
        return hasPaymentChanges(paymentUIState.editing, paymentState.map);
      case QuoteEditScreen.route:
        return hasQuoteChanges(quoteUIState.editing, quoteState.map);
      case ProjectEditScreen.route:
        return hasProjectChanges(projectUIState.editing, projectState.map);
      case TaskEditScreen.route:
        return hasTaskChanges(taskUIState.editing, taskState.map);
      case VendorEditScreen.route:
        return hasVendorChanges(vendorUIState.editing, vendorState.map);
      case ExpenseEditScreen.route:
        return hasExpenseChanges(expenseUIState.editing, expenseState.map);
      case GroupEditScreen.route:
        return hasGroupChanges(groupUIState.editing, groupState.map);
      case TaxRateEditScreen.route:
        return hasTaxRateChanges(taxRateUIState.editing, taxRateState.map);
      case CompanyGatewayEditScreen.route:
        return hasCompanyGatewayChanges(
            companyGatewayUIState.editing, companyGatewayState.map);
      case CreditEditScreen.route:
        return hasCreditChanges(creditUIState.editing, creditState.map);
      // STARTER: has changes - do not remove comment
      case PaymentTermEditScreen.route:
        return hasPaymentTermChanges(
            paymentTermUIState.editing, paymentTermState.map);
      case DesignEditScreen.route:
        return hasDesignChanges(designUIState.editing, designState.map);
    }

    if (uiState.currentRoute.startsWith('/settings')) {
      return settingsUIState.isChanged;
    }

    if (uiState.currentRoute.endsWith('/edit') ||
        uiState.currentRoute.endsWith('_edit')) {
      throw 'AppState.hasChanges is not defined for ${uiState.currentRoute}';
    }

    return false;
  }

  bool supportsVersion(String version) {
    final parts = version.split('.');
    final int major = int.parse(parts[0]);
    final int minor = int.parse(parts[1]);
    final int patch = int.parse(parts[2]);

    try {
      final serverParts = account.currentVersion.split('.');
      final int serverMajor = int.parse(serverParts[0]);
      final int serverMinor = int.parse(serverParts[1]);
      final int serverPatch = int.parse(serverParts[2]);

      return serverMajor >= major &&
          serverMinor >= minor &&
          serverPatch >= patch;
    } catch (e) {
      return false;
    }
  }

  bool get reportErrors => account?.reportErrors ?? false;

  bool get isHosted => authState.isHosted ?? false;

  bool get isSelfHosted => authState.isSelfHost ?? false;

  bool get isMenuCollapsed =>
      (prefState.isNotMobile &&
          prefState.fullHeightFilter &&
          prefState.showMenu &&
          uiState.filterEntityType != null) ||
      prefState.isMenuCollapsed;

  @override
  String toString() {
    //return 'Permissions: ${uiState.userUIState.editing.userCompany.permissions}';
    //return 'Layout: ${uiState.layout}, menu: ${uiState.menuSidebarMode}, history: ${uiState.historySidebarMode}';
    //return 'Gateway: ${uiState.companyGatewayUIState.editing.feesAndLimitsMap}';
    //return 'HISTORY: ${uiState.historyList.map((history) => '${history.id}-${history.entityType}')}';
    //return 'Products: ' + productState.list.map((productId) => productState.map[productId].archivedAt).toList().join('-');
    //return 'resetCounterFrequencyId: ${settingsUIState.settings.resetCounterFrequencyId}';
    //return 'Fields: ${uiState.settingsUIState.company.customFields} - ${company.customFields}';
    //return 'Custom: ${uiState.settingsUIState.company.settings.customValue1} - ${company.settings.customValue1}';
    //return 'Color: $accentColor, User Settings: ${user?.userCompany?.settings}';
    //return 'Line items: ${invoiceUIState.editing.lineItems.map((item) => item.productKey)}';
    //return 'Settings: ${uiState.settingsUIState.settings}';
    //return 'Payment: ${uiState.paymentUIState.editing.paymentables}';
    //return 'isAuthenticated: ${authState.isAuthenticated}';
    //return 'MultiSelect: ${productListState.isInMultiselect()} SelectedIds: ${productListState.selectedIds}';
    //return 'History: $historyList';
    //return 'Report State: ${uiState.reportsUIState}';
    //return 'UserCompany Settngs: ${userCompany.settings}, User Settings: ${user.userCompany.settings}';
    //return 'Currency Map: ${staticState.currencyMap}';
    //return 'History: $historyList';
    //return 'Use inclusive: ${invoiceUIState.editing.usesInclusiveTaxes}';
    //return 'Invitations: ${invoiceUIState.editing.invitations}';
    //return 'Token: ${userCompanyStates.map((state) => state.token.token).where((name) => name.isNotEmpty).toList().join(', ')}';
    //return 'Settings: ${company.settings.companyLogo}';
    //return 'Designs: ${designState.list.map((id) => id + ' ' + designState.map[id].name).toList()}';
    //return 'PDF Variables: ${uiState.settingsUIState.settings.pdfVariables}';
    //return 'Account: ${userCompany.account}';
    //return 'Notifications: ${user.userCompany.notifications} ${uiState.settingsUIState.user.userCompany.notifications}';
    //return 'SORT: Sort Ascending: ${uiState.productUIState.listUIState.sortAscending}';
    //return 'URL: ${authState.url}';
    //return 'PLAN: ${account.plan}';
    //return 'Invoice ${invoiceUIState.editing}';
    //return 'Account: $account';
    //return 'Payment Terms: ${paymentTermState.map}';
    //return 'Selected client: ${uiState.clientUIState.selectedId}, Filter: ${uiState.filterEntityType} ${uiState.filterEntityId}';
    return 'Layout: ${prefState.appLayout}, Route: ${uiState.currentRoute} Prev: ${uiState.previousRoute}';
  }
}

class Credentials {
  Credentials({this.url, this.token});

  String url;
  String token;
}
