"use strict";

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

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

    const gvl = parseGvlSnapshotRawJson(snapshot.rawJson);

    if (!gvl) {
      return warnAndReturnGvlVendorNormalizerSummary(
        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 warnAndReturnGvlVendorNormalizerSummary(
        summary,
        "missing_vendor_list_version"
      );
    }

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

    const vendorRecords = 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 ?? parseNumericVendorId(vendorKey);

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

        records.push(
          buildGvlVendorRecord({
            vendor: vendor,
            vendorId: vendorId,
            vendorListVersion: vendorListVersion,
            snapshot: snapshot,
            gvl: gvl,
            normalizedAt: normalizedAt
          })
        );

        return records;
      },
      []
    );

    summary.vendorsWritten = await putGvlVendorRecords(vendorRecords);
    summary.ok = true;

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

    return summary;
  }
}

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

  try {
    const snapshot = await getLatestGvlSnapshot();

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

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

    return summary;
  }
}

function parseGvlSnapshotRawJson(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 buildGvlVendorRecord({
  vendor,
  vendorId,
  vendorListVersion,
  snapshot,
  gvl,
  normalizedAt
}) {
  return {
    id: `${vendorListVersion}:vendor:${vendorId}`,
    entityType: "vendor",
    vendorListVersion: vendorListVersion,
    vendorId: vendorId,
    snapshotSha256: snapshot.sha256,
    snapshotFetchedAt: snapshot.fetchedAt ?? null,
    gvlLastUpdated: gvl.lastUpdated ?? null,
    tcfPolicyVersion: gvl.tcfPolicyVersion ?? null,
    gvlSpecificationVersion: gvl.gvlSpecificationVersion ?? null,
    normalizedAt: normalizedAt,
    name: vendor.name ?? null,
    policyUrl: vendor.policyUrl ?? null,
    deletedDate: vendor.deletedDate ?? null,
    cookieMaxAgeSeconds: vendor.cookieMaxAgeSeconds ?? null,
    usesCookies: vendor.usesCookies ?? null,
    usesNonCookieAccess: vendor.usesNonCookieAccess ?? null,
    deviceStorageDisclosureUrl: vendor.deviceStorageDisclosureUrl ?? null,
    legitimateInterestDisclosureUrl:
      vendor.legitimateInterestDisclosureUrl ?? null,
    domains: vendor.domains ?? null,
    rawVendor: vendor
  };
}

function parseNumericVendorId(value) {
  const vendorId = Number(value);

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

  return vendorId;
}

async function putGvlVendorRecords(vendorRecords) {
  if (!vendorRecords.length) {
    return 0;
  }

  const db = await openVendorGetDb();

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

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

    vendorRecords.forEach((record) => {
      const putRequest = vendorsStore.put(record);

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

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

async function getLatestGvlSnapshot() {
  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(sortGvlSnapshotsNewestFirst(snapshots)[0] ?? null);
    };
  });
}

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

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

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

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

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

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

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

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

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

  return summary;
}

globalThis.normalizeGvlVendorsFromSnapshot = normalizeGvlVendorsFromSnapshot;
globalThis.normalizeLatestGvlSnapshotVendors = normalizeLatestGvlSnapshotVendors;
