import { createHash } from "node:crypto";

const PACKAGE_NAME = "com.makecdev.coderemotemanager";
const PRODUCT_ID = "pro_multi_account";
const SCHEMA_VERSION = 1;
const RETENTION_MS = 180 * 24 * 60 * 60 * 1000;
const INVALIDATION_REASONS = new Set(["canceled", "refunded", "voided"]);

export function hashPurchaseToken({ packageName, productId, purchaseToken }) {
  validateIdentity({ packageName, productId, purchaseToken });
  return createHash("sha256")
    .update(packageName)
    .update("\0")
    .update(productId)
    .update("\0")
    .update(purchaseToken)
    .digest("hex");
}

export function createPurchaseStateStore({ firestore, now = Date.now }) {
  if (typeof firestore?.collection !== "function" || typeof firestore?.runTransaction !== "function" || typeof now !== "function") {
    throw purchaseStateError("invalid_purchase_store_config", 500, "Purchase storage configuration is invalid.");
  }
  const purchases = firestore.collection("purchases");

  function time() {
    const value = now();
    const milliseconds = value instanceof Date ? value.getTime() : Number(value);
    if (!Number.isFinite(milliseconds)) throw purchaseStateError("purchase_state_unavailable", 503, "Purchase storage is unavailable.");
    return milliseconds;
  }

  return {
    async recordActive(input) {
      const purchaseKey = hashPurchaseToken(input);
      if (typeof input.purchaseCompletionTime !== "string" || !Number.isFinite(Date.parse(input.purchaseCompletionTime))) {
        throw purchaseStateError("purchase_state_invalid", 500, "Purchase state is invalid.");
      }
      const reference = purchases.doc(purchaseKey);
      const timestamp = time();
      await firestore.runTransaction(async (transaction) => {
        const snapshot = await transaction.get(reference);
        const existing = snapshot.exists ? snapshot.data() : null;
        if (existing) {
          validateStoredState(existing);
          if (existing.state === "invalidated") {
            throw purchaseStateError("purchase_invalidated", 402, "The purchase is no longer valid.");
          }
        }
        transaction.set(reference, {
          schemaVersion: SCHEMA_VERSION,
          state: "active",
          packageName: PACKAGE_NAME,
          productId: PRODUCT_ID,
          purchaseCompletionTime: input.purchaseCompletionTime,
          createdAt: existing?.createdAt ?? new Date(timestamp),
          lastVerifiedAt: new Date(timestamp),
        });
      });
      return { purchaseKey, state: "active" };
    },

    async invalidate(input) {
      const purchaseKey = hashPurchaseToken(input);
      if (!INVALIDATION_REASONS.has(input.reason)) {
        throw purchaseStateError("invalid_invalidation_reason", 400, "The invalidation reason is invalid.");
      }
      const reference = purchases.doc(purchaseKey);
      const timestamp = time();
      await firestore.runTransaction(async (transaction) => {
        const snapshot = await transaction.get(reference);
        const existing = snapshot.exists ? snapshot.data() : null;
        if (existing) validateStoredState(existing);
        if (existing?.state === "invalidated") return;
        transaction.set(reference, {
          schemaVersion: SCHEMA_VERSION,
          state: "invalidated",
          packageName: PACKAGE_NAME,
          productId: PRODUCT_ID,
          invalidatedAt: new Date(timestamp),
          invalidationReason: input.reason,
          deleteAfter: new Date(timestamp + RETENTION_MS),
        });
      });
      return { purchaseKey, state: "invalidated" };
    },
  };
}

function validateIdentity({ packageName, productId, purchaseToken } = {}) {
  if (
    packageName !== PACKAGE_NAME
    || productId !== PRODUCT_ID
    || typeof purchaseToken !== "string"
    || purchaseToken.length === 0
    || purchaseToken.length > 4096
  ) throw purchaseStateError("purchase_identity_invalid", 400, "The purchase identity is invalid.");
}

function validateStoredState(value) {
  const common = value?.schemaVersion === SCHEMA_VERSION
    && value.packageName === PACKAGE_NAME
    && value.productId === PRODUCT_ID;
  const validActive = common
    && value.state === "active"
    && exactKeys(value, ["createdAt", "lastVerifiedAt", "packageName", "productId", "purchaseCompletionTime", "schemaVersion", "state"])
    && typeof value.purchaseCompletionTime === "string"
    && Number.isFinite(Date.parse(value.purchaseCompletionTime))
    && validTimestamp(value.createdAt)
    && validTimestamp(value.lastVerifiedAt);
  const validInvalidated = common
    && value.state === "invalidated"
    && exactKeys(value, ["deleteAfter", "invalidatedAt", "invalidationReason", "packageName", "productId", "schemaVersion", "state"])
    && INVALIDATION_REASONS.has(value.invalidationReason)
    && validTimestamp(value.invalidatedAt)
    && validTimestamp(value.deleteAfter);
  if (!validActive && !validInvalidated) {
    throw purchaseStateError("purchase_state_invalid", 500, "Purchase state is invalid.");
  }
}

function exactKeys(value, expected) {
  const keys = Object.keys(value).sort();
  return keys.length === expected.length && expected.every((key, index) => keys[index] === key);
}

function validTimestamp(value) {
  try {
    const date = value instanceof Date ? value : value?.toDate?.();
    return date instanceof Date && Number.isFinite(date.getTime());
  } catch {
    return false;
  }
}

function purchaseStateError(code, status, message) {
  const error = new Error(message);
  error.code = code;
  error.status = status;
  return error;
}
