import * as Constants from "./Constants";
import * as DataModels from "../Contracts/DataModels";
import * as ErrorParserUtility from "./ErrorParserUtility";
import * as ViewModels from "../Contracts/ViewModels";
import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
import * as Logger from "./Logger";
import { sendMessage } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";

// TODO: Log all promise resolutions and errors with verbosity levels
export function queryDocuments(
  databaseId: string,
  containerId: string,
  query: string,
  options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
  return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
}

export function queryConflicts(
  databaseId: string,
  containerId: string,
  query: string,
  options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
  return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
}

export function getEntityName() {
  const defaultExperience =
    window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
  if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
    return "document";
  }
  return "item";
}

export function readStoredProcedures(
  collection: ViewModels.Collection,
  options: any = {}
): Q.Promise<DataModels.StoredProcedure[]> {
  var deferred = Q.defer<DataModels.StoredProcedure[]>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying stored procedures for container ${collection.id()}`
  );
  DataAccessUtilityBase.readStoredProcedures(collection, options)
    .then(
      (storedProcedures: DataModels.StoredProcedure[]) => {
        deferred.resolve(storedProcedures);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to query stored procedures for container ${collection.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readStoredProcedure(
  collection: ViewModels.Collection,
  requestedResource: DataModels.Resource,
  options?: any
): Q.Promise<DataModels.StoredProcedure> {
  return DataAccessUtilityBase.readStoredProcedure(collection, requestedResource, options);
}

export function readUserDefinedFunctions(
  collection: ViewModels.Collection,
  options: any = {}
): Q.Promise<DataModels.UserDefinedFunction[]> {
  var deferred = Q.defer<DataModels.UserDefinedFunction[]>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying user defined functions for collection ${collection.id()}`
  );
  DataAccessUtilityBase.readUserDefinedFunctions(collection, options)
    .then(
      (userDefinedFunctions: DataModels.UserDefinedFunction[]) => {
        deferred.resolve(userDefinedFunctions);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to query user defined functions for container ${collection.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadUDFs", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readUserDefinedFunction(
  collection: ViewModels.Collection,
  requestedResource: DataModels.Resource,
  options: any
): Q.Promise<DataModels.UserDefinedFunction> {
  return DataAccessUtilityBase.readUserDefinedFunction(collection, requestedResource, options);
}

export function readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
  var deferred = Q.defer<DataModels.Trigger[]>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying triggers for container ${collection.id()}`
  );
  DataAccessUtilityBase.readTriggers(collection, options)
    .then(
      (triggers: DataModels.Trigger[]) => {
        deferred.resolve(triggers);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to query triggers for container ${collection.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadTriggers", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readTrigger(
  collection: ViewModels.Collection,
  requestedResource: DataModels.Resource,
  options?: any
): Q.Promise<DataModels.Trigger> {
  return DataAccessUtilityBase.readTrigger(collection, requestedResource, options);
}

export function executeStoredProcedure(
  collection: ViewModels.Collection,
  storedProcedure: StoredProcedure,
  partitionKeyValue: any,
  params: any[]
): Q.Promise<any> {
  var deferred = Q.defer<any>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Executing stored procedure ${storedProcedure.id()}`
  );
  DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
    .then(
      (response: any) => {
        deferred.resolve(response);
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
        );
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
            error
          )}`
        );
        Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function queryDocumentsPage(
  resourceName: string,
  documentsIterator: MinimalQueryIterator,
  firstItemIndex: number,
  options: any
): Q.Promise<ViewModels.QueryResults> {
  var deferred = Q.defer<ViewModels.QueryResults>();
  const entityName = getEntityName();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying ${entityName} for container ${resourceName}`
  );
  Q(nextPage(documentsIterator, firstItemIndex))
    .then(
      (result: ViewModels.QueryResults) => {
        const itemCount = (result.documents && result.documents.length) || 0;
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`
        );
        deferred.resolve(result);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
  var deferred = Q.defer<any>();
  const entityName = getEntityName();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Reading ${entityName} ${documentId.id()}`
  );
  DataAccessUtilityBase.readDocument(collection, documentId)
    .then(
      (document: any) => {
        deferred.resolve(document);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateCollection(
  databaseId: string,
  collection: ViewModels.Collection,
  newCollection: DataModels.Collection
): Q.Promise<DataModels.Collection> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Updating container ${collection.id()}`
  );
  DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
    .then(
      (replacedCollection: DataModels.Collection) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully updated container ${collection.id()}`
        );
        deferred.resolve(replacedCollection);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateDocument(
  collection: ViewModels.CollectionBase,
  documentId: ViewModels.DocumentId,
  newDocument: any
): Q.Promise<any> {
  var deferred = Q.defer<any>();
  const entityName = getEntityName();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Updating ${entityName} ${documentId.id()}`
  );
  DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
    .then(
      (updatedDocument: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully updated ${entityName} ${documentId.id()}`
        );
        deferred.resolve(updatedDocument);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateOffer(
  offer: DataModels.Offer,
  newOffer: DataModels.Offer,
  options: RequestOptions
): Q.Promise<DataModels.Offer> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Updating offer for resource ${offer.resource}`
  );
  DataAccessUtilityBase.updateOffer(offer, newOffer, options)
    .then(
      (replacedOffer: DataModels.Offer) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully updated offer for resource ${offer.resource}`
        );
        deferred.resolve(replacedOffer);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`
        );
        Logger.logError(
          JSON.stringify({
            oldOffer: offer,
            newOffer: newOffer,
            error: error
          }),
          "UpdateOffer",
          error.code
        );
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateOfferThroughputBeyondLimit(
  requestPayload: DataModels.UpdateOfferThroughputRequest
): Q.Promise<void> {
  const deferred: Q.Deferred<void> = Q.defer<void>();
  const resourceDescriptionInfo: string = requestPayload.collectionName
    ? `database ${requestPayload.databaseName} and container ${requestPayload.collectionName}`
    : `database ${requestPayload.databaseName}`;
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Requesting increase in throughput to ${requestPayload.throughput} for ${resourceDescriptionInfo}`
  );
  DataAccessUtilityBase.updateOfferThroughputBeyondLimit(requestPayload)
    .then(
      () => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully requested an increase in throughput to ${requestPayload.throughput} for ${resourceDescriptionInfo}`
        );
        deferred.resolve();
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Failed to request an increase in throughput for ${requestPayload.throughput}: ${JSON.stringify(error)}`
        );
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));

  return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
}

export function updateStoredProcedure(
  collection: ViewModels.Collection,
  storedProcedure: DataModels.StoredProcedure,
  options?: any
): Q.Promise<DataModels.StoredProcedure> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Updating stored procedure ${storedProcedure.id}`
  );
  DataAccessUtilityBase.updateStoredProcedure(collection, storedProcedure, options)
    .then(
      (updatedStoredProcedure: DataModels.StoredProcedure) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully updated stored procedure ${storedProcedure.id}`
        );
        deferred.resolve(updatedStoredProcedure);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateUserDefinedFunction(
  collection: ViewModels.Collection,
  userDefinedFunction: DataModels.UserDefinedFunction,
  options: any = {}
): Q.Promise<DataModels.UserDefinedFunction> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Updating user defined function ${userDefinedFunction.id}`
  );
  DataAccessUtilityBase.updateUserDefinedFunction(collection, userDefinedFunction, options)
    .then(
      (updatedUserDefinedFunction: DataModels.UserDefinedFunction) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully updated user defined function ${userDefinedFunction.id}`
        );
        deferred.resolve(updatedUserDefinedFunction);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "UpdateUDF", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function updateTrigger(
  collection: ViewModels.Collection,
  trigger: DataModels.Trigger
): Q.Promise<DataModels.Trigger> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Updating trigger ${trigger.id}`);
  DataAccessUtilityBase.updateTrigger(collection, trigger)
    .then(
      (updatedTrigger: DataModels.Trigger) => {
        NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Updated trigger ${trigger.id}`);
        deferred.resolve(updatedTrigger);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "UpdateTrigger", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
  var deferred = Q.defer<any>();
  const entityName = getEntityName();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating new ${entityName} for container ${collection.id()}`
  );
  DataAccessUtilityBase.createDocument(collection, newDocument)
    .then(
      (savedDocument: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created new ${entityName} for container ${collection.id()}`
        );
        deferred.resolve(savedDocument);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function createStoredProcedure(
  collection: ViewModels.Collection,
  newStoredProcedure: DataModels.StoredProcedure,
  options?: any
): Q.Promise<DataModels.StoredProcedure> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating stored procedure for container ${collection.id()}`
  );
  DataAccessUtilityBase.createStoredProcedure(collection, newStoredProcedure, options)
    .then(
      (createdStoredProcedure: DataModels.StoredProcedure) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created stored procedure for container ${collection.id()}`
        );
        deferred.resolve(createdStoredProcedure);
      },
      error => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function createUserDefinedFunction(
  collection: ViewModels.Collection,
  newUserDefinedFunction: DataModels.UserDefinedFunction,
  options?: any
): Q.Promise<DataModels.UserDefinedFunction> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating user defined function for container ${collection.id()}`
  );
  DataAccessUtilityBase.createUserDefinedFunction(collection, newUserDefinedFunction, options)
    .then(
      (createdUserDefinedFunction: DataModels.UserDefinedFunction) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created user defined function for container ${collection.id()}`
        );
        deferred.resolve(createdUserDefinedFunction);
      },
      error => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "CreateUDF", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function createTrigger(
  collection: ViewModels.Collection,
  newTrigger: DataModels.Trigger,
  options: any = {}
): Q.Promise<DataModels.Trigger> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating trigger for container ${collection.id()}`
  );
  DataAccessUtilityBase.createTrigger(collection, newTrigger, options)
    .then(
      (createdTrigger: DataModels.Trigger) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created trigger for container ${collection.id()}`
        );
        deferred.resolve(createdTrigger);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating trigger for container ${collection.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "CreateTrigger", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteDocument(
  collection: ViewModels.CollectionBase,
  documentId: ViewModels.DocumentId
): Q.Promise<any> {
  var deferred = Q.defer<any>();
  const entityName = getEntityName();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting ${entityName} ${documentId.id()}`
  );
  DataAccessUtilityBase.deleteDocument(collection, documentId)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted ${entityName} ${documentId.id()}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteConflict(
  collection: ViewModels.CollectionBase,
  conflictId: ViewModels.ConflictId,
  options?: any
): Q.Promise<any> {
  var deferred = Q.defer<any>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting conflict ${conflictId.id()}`
  );
  DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted conflict ${conflictId.id()}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteCollection(collection: ViewModels.Collection, options: any = {}): Q.Promise<any> {
  var deferred = Q.defer<any>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting container ${collection.id()}`
  );
  DataAccessUtilityBase.deleteCollection(collection, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted container ${collection.id()}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting container ${collection.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteCollection", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteDatabase(database: ViewModels.Database, options: any = {}): Q.Promise<any> {
  var deferred = Q.defer<any>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting database ${database.id()}`
  );
  DataAccessUtilityBase.deleteDatabase(database, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted database ${database.id()}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting database ${database.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteDatabase", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteStoredProcedure(
  collection: ViewModels.Collection,
  storedProcedure: DataModels.StoredProcedure,
  options: any = {}
): Q.Promise<DataModels.StoredProcedure> {
  var deferred = Q.defer<any>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting stored procedure ${storedProcedure.id}`
  );
  DataAccessUtilityBase.deleteStoredProcedure(collection, storedProcedure, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted stored procedure ${storedProcedure.id}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteUserDefinedFunction(
  collection: ViewModels.Collection,
  userDefinedFunction: DataModels.UserDefinedFunction,
  options: any = {}
): Q.Promise<DataModels.UserDefinedFunction> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Deleting user defined function ${userDefinedFunction.id}`
  );
  DataAccessUtilityBase.deleteUserDefinedFunction(collection, userDefinedFunction, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully deleted user defined function ${userDefinedFunction.id}`
        );
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteUDF", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function deleteTrigger(
  collection: ViewModels.Collection,
  trigger: DataModels.Trigger,
  options: any = {}
): Q.Promise<DataModels.Trigger> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Deleting trigger ${trigger.id}`);
  DataAccessUtilityBase.deleteTrigger(collection, trigger, options)
    .then(
      (response: any) => {
        NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully deleted trigger ${trigger.id}`);
        deferred.resolve(response);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while deleting trigger ${trigger.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "DeleteTrigger", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function refreshCachedResources(options: any = {}): Q.Promise<void> {
  return DataAccessUtilityBase.refreshCachedResources(options);
}

export function refreshCachedOffers(): Q.Promise<void> {
  return DataAccessUtilityBase.refreshCachedOffers();
}

export function readCollections(database: ViewModels.Database, options: any = {}): Q.Promise<DataModels.Collection[]> {
  var deferred = Q.defer<DataModels.Collection[]>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying containers for database ${database.id()}`
  );
  DataAccessUtilityBase.readCollections(database, options)
    .then(
      (collections: DataModels.Collection[]) => {
        deferred.resolve(collections);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying containers for database ${database.id()}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadCollections", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readCollection(databaseId: string, collectionId: string): Q.Promise<DataModels.Collection> {
  const deferred = Q.defer<DataModels.Collection>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying container ${collectionId}`
  );

  DataAccessUtilityBase.readCollection(databaseId, collectionId)
    .then(
      (collection: DataModels.Collection) => {
        deferred.resolve(collection);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadCollections", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readCollectionQuotaInfo(
  collection: ViewModels.Collection,
  options?: any
): Q.Promise<DataModels.CollectionQuotaInfo> {
  var deferred = Q.defer<DataModels.CollectionQuotaInfo>();

  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Querying quota info for container ${collection.id}`
  );
  DataAccessUtilityBase.readCollectionQuotaInfo(collection, options)
    .then(
      (quota: DataModels.CollectionQuotaInfo) => {
        deferred.resolve(quota);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
  var deferred = Q.defer<DataModels.Offer[]>();

  const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers");
  DataAccessUtilityBase.readOffers(options)
    .then(
      (offers: DataModels.Offer[]) => {
        deferred.resolve(offers);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying offers:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadOffers", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readOffer(
  requestedResource: DataModels.Offer,
  options: any = {}
): Q.Promise<DataModels.OfferWithHeaders> {
  var deferred = Q.defer<DataModels.OfferWithHeaders>();

  const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer");
  DataAccessUtilityBase.readOffer(requestedResource, options)
    .then(
      (offer: DataModels.OfferWithHeaders) => {
        deferred.resolve(offer);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying offer:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadOffer", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function readDatabases(options: any): Q.Promise<DataModels.Database[]> {
  var deferred = Q.defer<any>();
  const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying databases");
  DataAccessUtilityBase.readDatabases(options)
    .then(
      (databases: DataModels.Database[]) => {
        deferred.resolve(databases);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while querying databases:\n ${JSON.stringify(error)}`
        );
        Logger.logError(JSON.stringify(error), "ReadDatabases", error.code);
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => {
      NotificationConsoleUtils.clearInProgressMessageWithId(id);
    });

  return deferred.promise;
}

export function getOrCreateDatabaseAndCollection(
  request: DataModels.CreateDatabaseAndCollectionRequest,
  options: any = {}
): Q.Promise<DataModels.Collection> {
  const deferred: Q.Deferred<DataModels.Collection> = Q.defer<DataModels.Collection>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating a new container ${request.collectionId} for database ${request.databaseId}`
  );

  DataAccessUtilityBase.getOrCreateDatabaseAndCollection(request, options)
    .then(
      (collection: DataModels.Collection) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created container ${request.collectionId}`
        );
        deferred.resolve(collection);
      },
      (error: any) => {
        const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating container ${request.collectionId}:\n ${sanitizedError}`
        );
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));

  return deferred.promise;
}

export function createDatabase(
  request: DataModels.CreateDatabaseRequest,
  options: any = {}
): Q.Promise<DataModels.Database> {
  const deferred: Q.Deferred<DataModels.Database> = Q.defer<DataModels.Database>();
  const id = NotificationConsoleUtils.logConsoleMessage(
    ConsoleDataType.InProgress,
    `Creating a new database ${request.databaseId}`
  );

  DataAccessUtilityBase.createDatabase(request, options)
    .then(
      (database: DataModels.Database) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Info,
          `Successfully created database ${request.databaseId}`
        );
        deferred.resolve(database);
      },
      (error: any) => {
        NotificationConsoleUtils.logConsoleMessage(
          ConsoleDataType.Error,
          `Error while creating database ${request.databaseId}:\n ${JSON.stringify(error)}`
        );
        sendNotificationForError(error);
        deferred.reject(error);
      }
    )
    .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));

  return deferred.promise;
}

export function sendNotificationForError(error: any) {
  if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
    if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
      return;
    }
    sendMessage({
      type: MessageTypes.ForbiddenError,
      reason: error && error.message ? error.message : error
    });
  }
}