const STORAGE_KEY = "dbBinLibraries";
const REMOTE_MESSAGE_TYPES = {
  list: "BIN_LIBRARIES_LIST",
  create: "BIN_LIBRARY_CREATE",
  update: "BIN_LIBRARY_UPDATE",
  delete: "BIN_LIBRARY_DELETE"
};

function createLibraryId() {
  if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
    return globalThis.crypto.randomUUID();
  }

  return `lib_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
}

function splitBinText(value) {
  return String(value ?? "")
    .split(/\r?\n|,/)
    .map((item) => item.trim())
    .filter(Boolean);
}

function extractLibraryBins(library) {
  if (!library || typeof library !== "object") {
    return [];
  }

  return (
    library.bins ??
    library.binList ??
    library.bin_list ??
    library.entries ??
    library.cards ??
    library.values ??
    []
  );
}

function normalizeBinEntries(entries) {
  const source = Array.isArray(entries)
    ? entries
    : typeof entries === "string"
      ? splitBinText(entries)
      : [];
  const seen = new Set();
  const normalized = [];

  for (const entry of source) {
    const value = extractBinEntryValue(entry);
    if (!value || seen.has(value)) {
      continue;
    }
    seen.add(value);
    normalized.push(value);
  }

  return normalized;
}

function extractPrimitiveValue(entry) {
  if (entry == null) {
    return "";
  }

  if (typeof entry === "string" || typeof entry === "number") {
    return String(entry).trim();
  }

  return "";
}

function extractBinEntryValue(entry) {
  const primitiveValue = extractPrimitiveValue(entry);
  if (primitiveValue && primitiveValue !== "[object Object]") {
    return primitiveValue;
  }

  if (Array.isArray(entry)) {
    return normalizeBinEntries(entry).join(", ");
  }

  if (!entry || typeof entry !== "object") {
    return "";
  }

  const directKeys = [
    "value",
    "bin",
    "card",
    "cardNumber",
    "number",
    "text",
    "label",
    "display",
    "masked",
    "raw",
    "entry",
    "prefix",
    "pattern"
  ];

  for (const key of directKeys) {
    const directValue = extractPrimitiveValue(entry[key]);
    if (directValue && directValue !== "[object Object]") {
      return directValue;
    }
  }

  const compositeCardNumber = extractPrimitiveValue(
    entry.cc ??
      entry.pan ??
      entry.card_no ??
      entry.cardNo ??
      entry.card_number ??
      entry.number
  );
  const compositeMonth = extractPrimitiveValue(entry.mm ?? entry.month ?? entry.exp_month);
  const compositeYear = extractPrimitiveValue(entry.yy ?? entry.year ?? entry.exp_year);
  const compositeCvc = extractPrimitiveValue(entry.cvv ?? entry.cvc ?? entry.security_code);

  if (compositeCardNumber) {
    return [compositeCardNumber, compositeMonth, compositeYear, compositeCvc]
      .filter(Boolean)
      .join("|");
  }

  const objectValues = Object.values(entry)
    .map((value) => extractPrimitiveValue(value))
    .filter((value) => value && value !== "[object Object]");

  if (objectValues.length) {
    return objectValues.join(" | ");
  }

  return "";
}

function sanitizeLibrary(library) {
  if (!library || typeof library !== "object") {
    return null;
  }

  const rawBins = extractLibraryBins(library);
  const bins = normalizeBinEntries(rawBins);
  const rawBinCount = Array.isArray(rawBins)
    ? rawBins.length
    : typeof rawBins === "string"
      ? splitBinText(rawBins).length
      : 0;

  const name = String(
    library.name ??
      library.title ??
      library.label ??
      library.libraryName ??
      library.library_name ??
      ""
  ).trim();

  if (!name) {
    return null;
  }

  return {
    id: String(
      library.id ??
        library._id ??
        library.libraryId ??
        library.library_id ??
        createLibraryId()
    ),
    name,
    bins,
    rawBinCount,
    isReadOnly: Boolean(
      library.isReadOnly ?? library.readOnly ?? library.is_read_only ?? false
    ),
    createdAt:
      library.createdAt ??
      library.created_at ??
      library.insertedAt ??
      new Date().toISOString(),
    updatedAt:
      library.updatedAt ??
      library.updated_at ??
      library.modifiedAt ??
      new Date().toISOString()
  };
}

function sortLibraries(libraries) {
  return [...libraries].sort((left, right) => left.name.localeCompare(right.name));
}

async function readLocalLibraries() {
  const stored = await chrome.storage.local.get([STORAGE_KEY]);
  const libraries = Array.isArray(stored?.[STORAGE_KEY]) ? stored[STORAGE_KEY] : [];

  return sortLibraries(libraries.map(sanitizeLibrary).filter(Boolean));
}

async function writeLocalLibraries(libraries) {
  const normalized = sortLibraries(libraries.map(sanitizeLibrary).filter(Boolean));
  await chrome.storage.local.set({ [STORAGE_KEY]: normalized });
  return normalized;
}

function extractLibrariesFromPayload(payload) {
  if (Array.isArray(payload)) {
    return payload;
  }

  if (!payload || typeof payload !== "object") {
    return [];
  }

  if (Array.isArray(payload.libraries)) {
    return payload.libraries;
  }

  if (Array.isArray(payload.items)) {
    return payload.items;
  }

  if (Array.isArray(payload.results)) {
    return payload.results;
  }

  if (Array.isArray(payload.data)) {
    return payload.data;
  }

  if (payload.data && typeof payload.data === "object") {
    return extractLibrariesFromPayload(payload.data);
  }

  if (payload.library) {
    return [payload.library];
  }

  if (payload.item) {
    return [payload.item];
  }

  return [];
}

function mergeLibraries(primaryLibraries, secondaryLibraries = []) {
  const merged = [];
  const seenNames = new Set();
  const seenIds = new Set();

  for (const candidate of [...primaryLibraries, ...secondaryLibraries]) {
    const library = sanitizeLibrary(candidate);
    if (!library) {
      continue;
    }

    const normalizedName = library.name.toLowerCase();
    if (seenNames.has(normalizedName) || seenIds.has(library.id)) {
      continue;
    }

    seenNames.add(normalizedName);
    seenIds.add(library.id);
    merged.push(library);
  }

  return sortLibraries(merged);
}

function buildUniqueName(name, libraries, excludeId = "") {
  const baseName = String(name ?? "").trim();
  if (!baseName) {
    throw new Error("Library name is required");
  }

  const lowered = baseName.toLowerCase();
  const hasConflict = libraries.some(
    (library) => library.id !== excludeId && library.name.toLowerCase() === lowered
  );

  if (!hasConflict) {
    return baseName;
  }

  let index = 2;
  while (true) {
    const candidate = `${baseName} (${index})`;
    const candidateLowered = candidate.toLowerCase();
    const exists = libraries.some(
      (library) => library.id !== excludeId && library.name.toLowerCase() === candidateLowered
    );
    if (!exists) {
      return candidate;
    }
    index += 1;
  }
}

function ensureEditable(library) {
  if (!library) {
    throw new Error("Library not found");
  }

  if (library.isReadOnly) {
    throw new Error("This library is read-only");
  }
}

function buildExportPayload(library) {
  return JSON.stringify(
    {
      version: 1,
      exportedAt: new Date().toISOString(),
      library: {
        name: library.name,
        bins: library.bins,
        isReadOnly: false
      }
    },
    null,
    2
  );
}

function parseImportedLibraries(rawText) {
  const text = String(rawText ?? "").trim();
  if (!text) {
    throw new Error("Library file is empty");
  }

  try {
    const parsed = JSON.parse(text);
    const libraries = extractLibrariesFromPayload(parsed);

    if (libraries.length) {
      return libraries.map(sanitizeLibrary).filter(Boolean);
    }

    const library = sanitizeLibrary(parsed?.library ?? parsed);
    if (library) {
      return [library];
    }
  } catch {
    const bins = splitBinText(text);
    if (!bins.length) {
      throw new Error("Invalid library format");
    }

    return [
      {
        id: createLibraryId(),
        name: "Imported Library",
        bins,
        isReadOnly: false,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      }
    ];
  }

  throw new Error("Invalid library format");
}

function buildRemoteLibraryPayload(library) {
  const bins = normalizeBinEntries(library?.bins);
  const name = String(library?.name ?? "").trim();
  const id = library?.id ? String(library.id) : "";

  return {
    ...(id ? { id, libraryId: id, library_id: id } : {}),
    name,
    title: name,
    bins,
    binList: bins,
    bin_list: bins,
    binsText: bins.join("\n"),
    entries: bins
  };
}

function getRemoteErrorMessage(result, fallback) {
  return String(
    result?.error ??
      result?.data?.error ??
      result?.data?.message ??
      fallback
  );
}

async function sendRuntimeMessage(message) {
  if (!chrome?.runtime?.sendMessage) {
    return { success: false, error: "Runtime messaging unavailable" };
  }

  return new Promise((resolve) => {
    try {
      chrome.runtime.sendMessage(message, (response) => {
        if (chrome.runtime.lastError) {
          resolve({ success: false, error: chrome.runtime.lastError.message });
          return;
        }

        resolve(response || { success: false, error: "Empty response" });
      });
    } catch (error) {
      resolve({ success: false, error: error?.message || "Runtime messaging failed" });
    }
  });
}

async function requestRemoteLibraries() {
  const result = await sendRuntimeMessage({ type: REMOTE_MESSAGE_TYPES.list });

  if (!result?.success) {
    throw new Error(getRemoteErrorMessage(result, "Failed to load BIN libraries"));
  }

  return sortLibraries(extractLibrariesFromPayload(result.data).map(sanitizeLibrary).filter(Boolean));
}

async function getAllLibrariesWithFallback() {
  const localLibraries = await readLocalLibraries();

  try {
    const remoteLibraries = await requestRemoteLibraries();
    if (!remoteLibraries.length) {
      return localLibraries;
    }

    const mergedLibraries = mergeLibraries(remoteLibraries, localLibraries);
    await writeLocalLibraries(mergedLibraries);
    return mergedLibraries;
  } catch {
    return localLibraries;
  }
}

async function callRemoteMutation(type, payload) {
  const result = await sendRuntimeMessage({ type, payload });

  if (!result?.success) {
    throw new Error(getRemoteErrorMessage(result, "BIN library request failed"));
  }

  return result.data;
}

function resolveLibraryFromMutationResult(payload, fallbackName = "", fallbackId = "") {
  const libraries = extractLibrariesFromPayload(payload).map(sanitizeLibrary).filter(Boolean);
  if (libraries.length) {
    if (fallbackId) {
      const matchingById = libraries.find((library) => library.id === fallbackId);
      if (matchingById) {
        return matchingById;
      }
    }

    if (fallbackName) {
      const matchingByName = libraries.find(
        (library) => library.name.toLowerCase() === fallbackName.toLowerCase()
      );
      if (matchingByName) {
        return matchingByName;
      }
    }

    return libraries[libraries.length - 1];
  }

  return sanitizeLibrary(payload?.library ?? payload?.item ?? payload?.data ?? payload);
}

export function createBinLibraryManager() {
  return {
    async getAllLibraries() {
      return getAllLibrariesWithFallback();
    },

    async createLibrary(name, bins = []) {
      const currentLibraries = await getAllLibrariesWithFallback();
      const normalizedName = buildUniqueName(name, currentLibraries);
      const normalizedBins = normalizeBinEntries(bins);

      try {
        const remoteData = await callRemoteMutation(
          REMOTE_MESSAGE_TYPES.create,
          buildRemoteLibraryPayload({ name: normalizedName, bins: normalizedBins })
        );
        const remoteLibraries = extractLibrariesFromPayload(remoteData)
          .map(sanitizeLibrary)
          .filter(Boolean);

        if (remoteLibraries.length) {
          const mergedLibraries = mergeLibraries(remoteLibraries, currentLibraries);
          await writeLocalLibraries(mergedLibraries);
          return (
            remoteLibraries.find(
              (library) => library.name.toLowerCase() === normalizedName.toLowerCase()
            ) || remoteLibraries[remoteLibraries.length - 1]
          );
        }

        const remoteLibrary = resolveLibraryFromMutationResult(remoteData, normalizedName);
        if (remoteLibrary) {
          await writeLocalLibraries(mergeLibraries([remoteLibrary], currentLibraries));
          return remoteLibrary;
        }
      } catch {}

      const localLibraries = await readLocalLibraries();
      const library = {
        id: createLibraryId(),
        name: buildUniqueName(normalizedName, localLibraries),
        bins: normalizedBins,
        isReadOnly: false,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };

      await writeLocalLibraries([...localLibraries, library]);
      return library;
    },

    async updateLibrary(libraryId, nextLibrary) {
      const currentLibraries = await getAllLibrariesWithFallback();
      const index = currentLibraries.findIndex((library) => library.id === String(libraryId));
      const current = index >= 0 ? currentLibraries[index] : null;
      ensureEditable(current);

      const updatedLibrary = {
        ...current,
        name: buildUniqueName(nextLibrary?.name, currentLibraries, current.id),
        bins: normalizeBinEntries(nextLibrary?.bins),
        updatedAt: new Date().toISOString()
      };

      try {
        const remoteData = await callRemoteMutation(
          REMOTE_MESSAGE_TYPES.update,
          buildRemoteLibraryPayload(updatedLibrary)
        );
        const remoteLibraries = extractLibrariesFromPayload(remoteData)
          .map(sanitizeLibrary)
          .filter(Boolean);

        if (remoteLibraries.length) {
          const remainingLibraries = currentLibraries.filter(
            (library) => library.id !== current.id
          );
          const mergedLibraries = mergeLibraries(remoteLibraries, remainingLibraries);
          await writeLocalLibraries(mergedLibraries);
          return (
            mergedLibraries.find((library) => library.id === current.id) ||
            mergedLibraries.find(
              (library) => library.name.toLowerCase() === updatedLibrary.name.toLowerCase()
            ) ||
            updatedLibrary
          );
        }

        const remoteLibrary = resolveLibraryFromMutationResult(
          remoteData,
          updatedLibrary.name,
          current.id
        );
        if (remoteLibrary) {
          const remainingLibraries = currentLibraries.filter(
            (library) => library.id !== current.id
          );
          await writeLocalLibraries(mergeLibraries([remoteLibrary], remainingLibraries));
          return remoteLibrary;
        }
      } catch {}

      const localLibraries = await readLocalLibraries();
      const localIndex = localLibraries.findIndex(
        (library) => library.id === String(libraryId)
      );

      if (localIndex < 0) {
        throw new Error("Library not found");
      }

      localLibraries[localIndex] = updatedLibrary;
      await writeLocalLibraries(localLibraries);
      return updatedLibrary;
    },

    async deleteLibrary(libraryId) {
      const currentLibraries = await getAllLibrariesWithFallback();
      const current = currentLibraries.find((library) => library.id === String(libraryId));
      ensureEditable(current);

      try {
        const remoteData = await callRemoteMutation(REMOTE_MESSAGE_TYPES.delete, {
          id: String(libraryId),
          libraryId: String(libraryId),
          library_id: String(libraryId)
        });
        const remoteLibraries = extractLibrariesFromPayload(remoteData)
          .map(sanitizeLibrary)
          .filter(Boolean);

        if (remoteLibraries.length) {
          const remainingLibraries = currentLibraries.filter(
            (library) => library.id !== String(libraryId)
          );
          await writeLocalLibraries(mergeLibraries(remoteLibraries, remainingLibraries));
          return true;
        }
      } catch {}

      const localLibraries = await readLocalLibraries();
      const filteredLibraries = localLibraries.filter(
        (library) => library.id !== String(libraryId)
      );
      await writeLocalLibraries(filteredLibraries);
      return true;
    },

    async copyLibrary(libraryId, nextName = "") {
      const libraries = await getAllLibrariesWithFallback();
      const current = libraries.find((library) => library.id === String(libraryId));
      if (!current) {
        throw new Error("Library not found");
      }

      return this.createLibrary(nextName || `${current.name} (Copy)`, current.bins);
    },

    async exportLibrary(libraryId) {
      const libraries = await getAllLibrariesWithFallback();
      const current = libraries.find((library) => library.id === String(libraryId));
      if (!current) {
        throw new Error("Library not found");
      }

      return buildExportPayload(current);
    },

    async importLibrary(rawText) {
      const importedLibraries = parseImportedLibraries(rawText);
      let lastImportedLibrary = null;

      for (const library of importedLibraries) {
        lastImportedLibrary = await this.createLibrary(library.name, library.bins);
      }

      return lastImportedLibrary;
    }
  };
}

export function attachBinLibraryManagerToWindow(manager, target = globalThis) {
  if (!manager || !target) {
    return;
  }

  target.BinLibraryManager = manager;
}
