"use strict";

let vendorGetDbPromise = null;

function openVendorGetDb() {
  if (vendorGetDbPromise) {
    return vendorGetDbPromise;
  }

  vendorGetDbPromise = new Promise((resolve, reject) => {
    const request = indexedDB.open(VENDORGET_DB_NAME, VENDORGET_DB_VERSION);

    request.onerror = () => reject(request.error);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;

      if (!db.objectStoreNames.contains("consent_states")) {
        const consentStates = db.createObjectStore("consent_states", {
          keyPath: "stateFingerprint"
        });

        consentStates.createIndex("pageOrigin", "page.origin", { unique: false });
        consentStates.createIndex("firstSeenAt", "firstSeenAt", { unique: false });
        consentStates.createIndex("lastSeenAt", "lastSeenAt", { unique: false });
      }

      if (!db.objectStoreNames.contains("consent_events")) {
        const consentEvents = db.createObjectStore("consent_events", {
          keyPath: "id",
          autoIncrement: true
        });

        consentEvents.createIndex("stateFingerprint", "stateFingerprint", {
          unique: false
        });

        consentEvents.createIndex("eventType", "eventType", { unique: false });
        consentEvents.createIndex("capturedAt", "capturedAt", { unique: false });
        consentEvents.createIndex("pageOrigin", "page.origin", { unique: false });
      }

      if (!db.objectStoreNames.contains("observed_requests")) {
        const observedRequests = db.createObjectStore("observed_requests", {
          keyPath: "requestFingerprint"
        });

        observedRequests.createIndex("lastSeenAt", "lastSeenAt", {
          unique: false
        });

        observedRequests.createIndex("firstSeenAt", "firstSeenAt", {
          unique: false
        });

        observedRequests.createIndex("requestOrigin", "request.origin", {
          unique: false
        });

        observedRequests.createIndex("requestType", "request.type", {
          unique: false
        });

        observedRequests.createIndex("requestThirdParty", "request.thirdParty", {
          unique: false
        });

        observedRequests.createIndex(
          "correlationStateFingerprint",
          "correlation.stateFingerprint",
          { unique: false }
        );
      }

      ensureGvlStores(db, event.target.transaction);
    };

    request.onsuccess = () => resolve(request.result);
  });

  return vendorGetDbPromise;
}

async function closeVendorGetDb() {
  if (!vendorGetDbPromise) {
    return;
  }

  try {
    const db = await vendorGetDbPromise;
    db.close();
  } finally {
    vendorGetDbPromise = null;
  }
}

async function deleteVendorGetDatabase() {
  await closeVendorGetDb();

  return new Promise((resolve, reject) => {
    const request = indexedDB.deleteDatabase(VENDORGET_DB_NAME);

    request.onerror = () => reject(request.error);
    request.onblocked = () => reject(new Error("database_delete_blocked"));
    request.onsuccess = () => {
      resolve({
        success: true
      });
    };
  });
}

function ensureGvlStores(db, upgradeTransaction) {
  if (!db.objectStoreNames.contains("gvl_raw_evidence")) {
    const gvlRawEvidence = db.createObjectStore("gvl_raw_evidence", {
      keyPath: "rawGvlSha256"
    });

    createIndexesIfMissing(gvlRawEvidence, [
      "canonicalGvlSha256",
      "vendorListVersion",
      "fetchedAt",
      "sourceUrl",
      "ingestionSource",
      "contentType",
      "parseStatus"
    ]);
  }

  const gvlRawEvidence = getUpgradeObjectStore(
    db,
    upgradeTransaction,
    "gvl_raw_evidence"
  );

  if (gvlRawEvidence) {
    createIndexesIfMissing(gvlRawEvidence, [
      "canonicalGvlSha256",
      "vendorListVersion",
      "fetchedAt",
      "sourceUrl",
      "ingestionSource",
      "contentType",
      "parseStatus"
    ]);
  }

  if (!db.objectStoreNames.contains("gvl_snapshots")) {
    const gvlSnapshots = db.createObjectStore("gvl_snapshots", {
      keyPath: "sha256"
    });

    createIndexesIfMissing(gvlSnapshots, [
      "gvlRevision",
      "vendorListVersion",
      "fetchedAt",
      "sourceUrl",
      "rawGvlSha256",
      "canonicalGvlSha256",
      "snapshotSha256"
    ]);
  }

  const gvlSnapshots = getUpgradeObjectStore(
    db,
    upgradeTransaction,
    "gvl_snapshots"
  );

  if (gvlSnapshots) {
    createIndexesIfMissing(gvlSnapshots, [
      "gvlRevision",
      "vendorListVersion",
      "fetchedAt",
      "sourceUrl",
      "rawGvlSha256",
      "canonicalGvlSha256",
      "snapshotSha256"
    ]);
  }

  if (!db.objectStoreNames.contains("gvl_snapshot_events")) {
    const gvlSnapshotEvents = db.createObjectStore("gvl_snapshot_events", {
      keyPath: "id",
      autoIncrement: true
    });

    createIndexesIfMissing(gvlSnapshotEvents, [
      "eventType",
      "capturedAt",
      "gvlRevision",
      "vendorListVersion",
      "sha256",
      "sourceUrl",
      "rawGvlSha256",
      "canonicalGvlSha256",
      "snapshotSha256"
    ]);
  }

  const gvlSnapshotEvents = getUpgradeObjectStore(
    db,
    upgradeTransaction,
    "gvl_snapshot_events"
  );

  if (gvlSnapshotEvents) {
    createIndexesIfMissing(gvlSnapshotEvents, [
      "eventType",
      "capturedAt",
      "gvlRevision",
      "vendorListVersion",
      "sha256",
      "sourceUrl",
      "rawGvlSha256",
      "canonicalGvlSha256",
      "snapshotSha256"
    ]);
  }

  ensureGvlRelationshipStores(db, upgradeTransaction);
}

function ensureGvlRelationshipStores(db, upgradeTransaction) {
  const gvlRelationshipStoreDefinitions = [
    {
      name: "gvl_vendors",
      indexes: [
        "vendorListVersion",
        "vendorId",
        "name",
        "policyUrl",
        "deletedDate",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_purposes",
      indexes: [
        "vendorListVersion",
        "purposeId",
        "name",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_special_purposes",
      indexes: [
        "vendorListVersion",
        "specialPurposeId",
        "name",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_features",
      indexes: [
        "vendorListVersion",
        "featureId",
        "name",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_special_features",
      indexes: [
        "vendorListVersion",
        "specialFeatureId",
        "name",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_data_categories",
      indexes: [
        "vendorListVersion",
        "dataCategoryId",
        "name",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    },
    {
      name: "gvl_vendor_relationships",
      indexes: [
        "vendorListVersion",
        "vendorId",
        "relationshipType",
        "relatedId",
        "rawGvlSha256",
        "canonicalGvlSha256",
        "snapshotSha256"
      ]
    }
  ];

  gvlRelationshipStoreDefinitions.forEach((storeDefinition) => {
    if (db.objectStoreNames.contains(storeDefinition.name)) {
      const objectStore = getUpgradeObjectStore(
        db,
        upgradeTransaction,
        storeDefinition.name
      );

      if (objectStore) {
        createIndexesIfMissing(objectStore, storeDefinition.indexes);
      }

      return;
    }

    const objectStore = db.createObjectStore(storeDefinition.name, {
      keyPath: "id"
    });

    createIndexesIfMissing(objectStore, storeDefinition.indexes);
  });
}

function getUpgradeObjectStore(db, upgradeTransaction, storeName) {
  if (!upgradeTransaction || !db.objectStoreNames.contains(storeName)) {
    return null;
  }

  return upgradeTransaction.objectStore(storeName);
}

function createIndexesIfMissing(objectStore, indexNames) {
  indexNames.forEach((indexName) => {
    if (objectStore.indexNames.contains(indexName)) {
      return;
    }

    objectStore.createIndex(indexName, indexName, { unique: false });
  });
}
