"use strict";

async function normalizeGvlVendorRelationshipsFromSnapshot(snapshot) {
  const summary = {
    ok: false,
    vendorListVersion: snapshot?.vendorListVersion ?? null,
    snapshotSha256: snapshot?.sha256 ?? null,
    snapshotFetchedAt: snapshot?.fetchedAt ?? null,
    gvlLastUpdated: null,
    vendorsSeen: 0,
    relationshipsSeen: 0,
    relationshipsWritten: 0,
    skipped: 0,
    error: null
  };

  try {
    if (!snapshot) {
      return warnAndReturnGvlVendorRelationshipSummary(
        summary,
        "missing_snapshot"
      );
    }

    const gvl = parseGvlVendorRelationshipRawJson(snapshot.rawJson);

    if (!gvl) {
      return warnAndReturnGvlVendorRelationshipSummary(
        summary,
        "invalid_snapshot_raw_json"
      );
    }

    const vendorListVersion =
      gvl.vendorListVersion ?? snapshot.vendorListVersion ?? null;
    const vendors = gvl.vendors;
    const normalizedAt = new Date().toISOString();

    summary.vendorListVersion = vendorListVersion;
    summary.gvlLastUpdated = gvl.lastUpdated ?? null;

    if (vendorListVersion === null || vendorListVersion === undefined) {
      return warnAndReturnGvlVendorRelationshipSummary(
        summary,
        "missing_vendor_list_version"
      );
    }

    if (!vendors || typeof vendors !== "object" || Array.isArray(vendors)) {
      return warnAndReturnGvlVendorRelationshipSummary(
        summary,
        "missing_vendors"
      );
    }

    const relationshipRecords = Object.entries(vendors).reduce(
      (records, [vendorKey, vendor]) => {
        summary.vendorsSeen += 1;

        if (!vendor || typeof vendor !== "object" || Array.isArray(vendor)) {
          summary.skipped += 1;
          return records;
        }

        const vendorId =
          vendor.id ?? parseGvlVendorRelationshipNumericId(vendorKey);

        if (vendorId === null || vendorId === undefined) {
          summary.skipped += 1;
          return records;
        }

        getGvlVendorRelationshipDefinitions().forEach((definition) => {
          const relatedIds = vendor[definition.vendorField];

          if (relatedIds === undefined || relatedIds === null) {
            return;
          }

          if (!Array.isArray(relatedIds)) {
            summary.skipped += 1;
            return;
          }

          relatedIds.forEach((relatedId) => {
            if (relatedId === null || relatedId === undefined) {
              summary.skipped += 1;
              return;
            }

            summary.relationshipsSeen += 1;
            records.push(
              buildGvlVendorRelationshipRecord({
                vendorListVersion: vendorListVersion,
                vendorId: vendorId,
                relationshipType: definition.relationshipType,
                relatedId: relatedId,
                snapshot: snapshot,
                gvl: gvl,
                normalizedAt: normalizedAt
              })
            );
          });
        });

        return records;
      },
      []
    );

    summary.relationshipsWritten = await putGvlVendorRelationshipRecords(
      relationshipRecords
    );
    summary.ok = true;

    return summary;
  } catch (error) {
    summary.error = error?.message ?? String(error);
    console.warn("GVL vendor relationship normalization failed", {
      error: error,
      snapshotSha256: summary.snapshotSha256,
      vendorListVersion: summary.vendorListVersion
    });

    return summary;
  }
}

async function normalizeLatestGvlVendorRelationships() {
  const summary = {
    ok: false,
    vendorListVersion: null,
    snapshotSha256: null,
    snapshotFetchedAt: null,
    gvlLastUpdated: null,
    vendorsSeen: 0,
    relationshipsSeen: 0,
    relationshipsWritten: 0,
    skipped: 0,
    error: null
  };

  try {
    const snapshot = await getLatestGvlVendorRelationshipSnapshot();

    if (!snapshot) {
      return warnAndReturnGvlVendorRelationshipSummary(
        summary,
        "no_gvl_snapshots_found"
      );
    }

    return normalizeGvlVendorRelationshipsFromSnapshot(snapshot);
  } catch (error) {
    summary.error = error?.message ?? String(error);
    console.warn("Latest GVL vendor relationship normalization failed", error);

    return summary;
  }
}

function parseGvlVendorRelationshipRawJson(rawJson) {
  if (!rawJson) {
    return null;
  }

  if (typeof rawJson === "string") {
    try {
      return JSON.parse(rawJson);
    } catch (error) {
      console.warn("GVL snapshot rawJson is not valid JSON", error);
      return null;
    }
  }

  if (typeof rawJson === "object" && !Array.isArray(rawJson)) {
    return rawJson;
  }

  return null;
}

function getGvlVendorRelationshipDefinitions() {
  return [
    {
      vendorField: "purposes",
      relationshipType: "purpose"
    },
    {
      vendorField: "legIntPurposes",
      relationshipType: "legIntPurpose"
    },
    {
      vendorField: "flexiblePurposes",
      relationshipType: "flexiblePurpose"
    },
    {
      vendorField: "specialPurposes",
      relationshipType: "specialPurpose"
    },
    {
      vendorField: "features",
      relationshipType: "feature"
    },
    {
      vendorField: "specialFeatures",
      relationshipType: "specialFeature"
    }
  ];
}

function buildGvlVendorRelationshipRecord({
  vendorListVersion,
  vendorId,
  relationshipType,
  relatedId,
  snapshot,
  gvl,
  normalizedAt
}) {
  return {
    id: `${vendorListVersion}:vendor:${vendorId}:${relationshipType}:${relatedId}`,
    vendorListVersion: vendorListVersion,
    vendorId: vendorId,
    relationshipType: relationshipType,
    relatedId: relatedId,
    snapshotSha256: snapshot.sha256,
    snapshotFetchedAt: snapshot.fetchedAt ?? null,
    gvlLastUpdated: gvl.lastUpdated ?? null,
    normalizedAt: normalizedAt
  };
}

function parseGvlVendorRelationshipNumericId(value) {
  const numericId = Number(value);

  if (!Number.isFinite(numericId)) {
    return null;
  }

  return numericId;
}

async function putGvlVendorRelationshipRecords(relationshipRecords) {
  if (!relationshipRecords.length) {
    return 0;
  }

  const db = await openVendorGetDb();

  if (
    !db.objectStoreNames.contains(
      VENDORGET_STORE_NAMES.gvlVendorRelationships
    )
  ) {
    throw new Error("missing_gvl_vendor_relationships_store");
  }

  return new Promise((resolve, reject) => {
    const tx = db.transaction(
      [VENDORGET_STORE_NAMES.gvlVendorRelationships],
      "readwrite"
    );
    const relationshipsStore = tx.objectStore(
      VENDORGET_STORE_NAMES.gvlVendorRelationships
    );
    let written = 0;

    relationshipRecords.forEach((record) => {
      const putRequest = relationshipsStore.put(record);

      putRequest.onsuccess = () => {
        written += 1;
      };
    });

    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
    tx.oncomplete = () => resolve(written);
  });
}

async function getLatestGvlVendorRelationshipSnapshot() {
  const db = await openVendorGetDb();

  if (!db.objectStoreNames.contains(VENDORGET_STORE_NAMES.gvlSnapshots)) {
    throw new Error("missing_gvl_snapshots_store");
  }

  return new Promise((resolve, reject) => {
    const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
    const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
    const getAllRequest = snapshotsStore.getAll();

    getAllRequest.onerror = () => reject(getAllRequest.error);
    getAllRequest.onsuccess = () => {
      const snapshots = getAllRequest.result ?? [];

      resolve(
        sortGvlVendorRelationshipSnapshotsNewestFirst(snapshots)[0] ?? null
      );
    };
  });
}

function sortGvlVendorRelationshipSnapshotsNewestFirst(snapshots) {
  return snapshots.slice().sort((left, right) => {
    const fetchedAtComparison =
      toGvlVendorRelationshipComparableTime(right.fetchedAt) -
      toGvlVendorRelationshipComparableTime(left.fetchedAt);

    if (fetchedAtComparison !== 0) {
      return fetchedAtComparison;
    }

    return (
      toGvlVendorRelationshipComparableNumber(right.vendorListVersion) -
      toGvlVendorRelationshipComparableNumber(left.vendorListVersion)
    );
  });
}

function toGvlVendorRelationshipComparableTime(value) {
  const timestamp = Date.parse(value ?? "");

  return Number.isNaN(timestamp) ? 0 : timestamp;
}

function toGvlVendorRelationshipComparableNumber(value) {
  const numberValue = Number(value);

  return Number.isFinite(numberValue) ? numberValue : 0;
}

function warnAndReturnGvlVendorRelationshipSummary(summary, error) {
  summary.error = error;

  console.warn("GVL vendor relationship normalization skipped", {
    error: error,
    snapshotSha256: summary.snapshotSha256,
    vendorListVersion: summary.vendorListVersion
  });

  return summary;
}

globalThis.normalizeGvlVendorRelationshipsFromSnapshot =
  normalizeGvlVendorRelationshipsFromSnapshot;
globalThis.normalizeLatestGvlVendorRelationships =
  normalizeLatestGvlVendorRelationships;
