import {PrismaClient} from "@prisma/client";
import {SessionMembership} from "@/features/auth/types";
import {prisma} from "@/lib/db/prisma";

const tenantScopedModels = new Set([
  "branch",
  "customerProfile",
  "document",
  "vehicle",
  "vehicleImage",
  "vehicleFeatureAssignment",
  "vehicleAvailabilityBlock",
  "maintenanceRecord",
  "booking",
  "bookingDriver",
  "bookingAddon",
  "bookingAddonItem",
  "coupon",
  "invoice",
  "paymentTransaction",
  "refundTransaction",
  "notification",
  "auditLog",
  "contactInquiry",
  "favoriteVehicle",
  "rentalContract"
]);

const whereScopedOperations = new Set([
  "findFirst",
  "findFirstOrThrow",
  "findMany",
  "count",
  "aggregate",
  "groupBy",
  "updateMany",
  "deleteMany"
]);

const createScopedOperations = new Set(["create", "createMany"]);

type JsonRecord = Record<string, unknown>;

export function assertTenantAccess(userTenantIds: string[], tenantId: string) {
  if (!userTenantIds.includes(tenantId)) {
    throw new Error("Unauthorized tenant access.");
  }
}

export function resolveTenantId(
  activeTenantId: string | null | undefined,
  memberships: SessionMembership[],
  requestedTenantSlug?: string | null
) {
  if (requestedTenantSlug) {
    const membership = memberships.find((item) => item.tenantSlug === requestedTenantSlug);
    return membership?.tenantId ?? null;
  }

  return activeTenantId ?? memberships.find((item) => item.isDefault)?.tenantId ?? memberships[0]?.tenantId ?? null;
}

export function buildTenantWhere<T extends object>(tenantId: string, where?: T) {
  return {
    ...(where ?? {}),
    tenantId
  };
}

function injectTenantId(value: unknown, tenantId: string): unknown {
  if (Array.isArray(value)) {
    return value.map((item) => injectTenantId(item, tenantId));
  }

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

  return {
    ...(value as JsonRecord),
    tenantId
  };
}

function scopeTenantArgs(tenantId: string, operation: string, args: JsonRecord = {}) {
  if (whereScopedOperations.has(operation)) {
    return {
      ...args,
      where: buildTenantWhere(tenantId, args.where as JsonRecord | undefined)
    };
  }

  if (createScopedOperations.has(operation)) {
    return {
      ...args,
      data: injectTenantId(args.data, tenantId)
    };
  }

  if (operation === "upsert") {
    return {
      ...args,
      create: injectTenantId(args.create, tenantId),
      update: args.update
    };
  }

  if (operation === "update") {
    return {
      ...args,
      where: buildTenantWhere(tenantId, args.where as JsonRecord | undefined)
    };
  }

  return args;
}

export function getTenantDb(tenantId: string, client: PrismaClient = prisma) {
  return new Proxy(client, {
    get(target, property, receiver) {
      const baseValue = Reflect.get(target, property, receiver);

      if (typeof property !== "string" || !tenantScopedModels.has(property) || !baseValue || typeof baseValue !== "object") {
        return baseValue;
      }

      return new Proxy(baseValue as object, {
        get(modelTarget, operation, modelReceiver) {
          const modelOperation = Reflect.get(modelTarget, operation, modelReceiver);

          if (typeof operation !== "string" || typeof modelOperation !== "function") {
            return modelOperation;
          }

          if (operation === "findUnique" || operation === "findUniqueOrThrow" || operation === "delete") {
            return () => {
              throw new Error(`Unsafe tenant-scoped operation "${operation}" is blocked. Use findFirst/findMany/deleteMany instead.`);
            };
          }

          return (args?: JsonRecord) => {
            const scopedArgs = scopeTenantArgs(tenantId, operation, args);
            return modelOperation.call(modelTarget, scopedArgs);
          };
        }
      });
    }
  });
}