import { and, desc, eq } from "drizzle-orm";

import { db } from "@/lib/db";
import { mediaAsset, mediaUsage } from "@/lib/db/schema";
import type { MediaKind, MediaSource, MediaUsageType } from "@/lib/db/enums";

type MediaAssetRow = typeof mediaAsset.$inferSelect;
type MediaUsageRow = typeof mediaUsage.$inferSelect;

export type MediaAssetView = Pick<
  MediaAssetRow,
  "id" | "source" | "kind" | "url" | "fileName" | "label" | "altText" | "mimeType" | "size" | "createdAt"
> & {
  usages: Array<Pick<MediaUsageRow, "id" | "usageType" | "entityType" | "entityId" | "fieldKey">>;
};

export type MediaOption = Pick<MediaAssetView, "id" | "kind" | "url" | "label" | "source">;

export type PortfolioMediaBindings = {
  coverAssetId: string | null;
  sectionAssetIds: Record<string, string>;
  assetIds: Record<string, string>;
};

function mapMediaAsset(asset: MediaAssetRow & { usages: MediaUsageRow[] }): MediaAssetView {
  return {
    id: asset.id,
    source: asset.source,
    kind: asset.kind,
    url: asset.url,
    fileName: asset.fileName,
    label: asset.label,
    altText: asset.altText,
    mimeType: asset.mimeType,
    size: asset.size,
    createdAt: asset.createdAt,
    usages: asset.usages.map((usage) => ({
      id: usage.id,
      usageType: usage.usageType,
      entityType: usage.entityType,
      entityId: usage.entityId,
      fieldKey: usage.fieldKey,
    })),
  };
}

export async function getAdminMediaAssets() {
  const assets = await db.query.mediaAsset.findMany({
    with: { usages: { orderBy: [desc(mediaUsage.createdAt)] } },
    orderBy: [desc(mediaAsset.createdAt)],
  });

  return assets.map(mapMediaAsset);
}

export async function getMediaOptions(filters?: { kind?: MediaKind }): Promise<MediaOption[]> {
  return db
    .select({
      id: mediaAsset.id,
      kind: mediaAsset.kind,
      url: mediaAsset.url,
      label: mediaAsset.label,
      source: mediaAsset.source,
    })
    .from(mediaAsset)
    .where(filters?.kind ? eq(mediaAsset.kind, filters.kind) : undefined)
    .orderBy(desc(mediaAsset.createdAt));
}

export async function getMediaAssetById(id: string) {
  const asset = await db.query.mediaAsset.findFirst({
    where: eq(mediaAsset.id, id),
    with: { usages: true },
  });

  return asset ? mapMediaAsset(asset) : null;
}

export async function createMediaAsset(input: {
  source: MediaSource;
  kind: MediaKind;
  url: string;
  fileName: string;
  label: string;
  altText?: string | null;
  mimeType?: string | null;
  size?: number | null;
}) {
  const [created] = await db
    .insert(mediaAsset)
    .values({
      source: input.source,
      kind: input.kind,
      url: input.url,
      fileName: input.fileName,
      label: input.label,
      altText: input.altText ?? null,
      mimeType: input.mimeType ?? null,
      size: input.size ?? null,
    })
    .returning();

  return created;
}

export async function replaceEntityMediaUsages(input: {
  entityType: string;
  entityId: string;
  usages: Array<{
    assetId: string;
    usageType: MediaUsageType;
    fieldKey: string;
  }>;
}) {
  await db.transaction(async (tx) => {
    await tx
      .delete(mediaUsage)
      .where(and(eq(mediaUsage.entityType, input.entityType), eq(mediaUsage.entityId, input.entityId)));

    if (input.usages.length === 0) {
      return;
    }

    await tx.insert(mediaUsage).values(
      input.usages.map((usage) => ({
        assetId: usage.assetId,
        usageType: usage.usageType,
        entityType: input.entityType,
        entityId: input.entityId,
        fieldKey: usage.fieldKey,
      })),
    );
  });
}

export async function deleteEntityMediaUsages(entityType: string, entityId: string) {
  await db
    .delete(mediaUsage)
    .where(and(eq(mediaUsage.entityType, entityType), eq(mediaUsage.entityId, entityId)));
}

export async function getPortfolioMediaBindings(projectId: string): Promise<PortfolioMediaBindings> {
  const usages = await db
    .select({
      assetId: mediaUsage.assetId,
      usageType: mediaUsage.usageType,
      fieldKey: mediaUsage.fieldKey,
    })
    .from(mediaUsage)
    .where(and(eq(mediaUsage.entityType, "portfolio-project"), eq(mediaUsage.entityId, projectId)));

  return usages.reduce<PortfolioMediaBindings>(
    (result, usage) => {
      if (usage.usageType === "PORTFOLIO_COVER") {
        result.coverAssetId = usage.assetId;
      }

      if (usage.usageType === "PORTFOLIO_SECTION") {
        result.sectionAssetIds[usage.fieldKey] = usage.assetId;
      }

      if (usage.usageType === "PORTFOLIO_ASSET") {
        result.assetIds[usage.fieldKey] = usage.assetId;
      }

      return result;
    },
    {
      coverAssetId: null,
      sectionAssetIds: {},
      assetIds: {},
    },
  );
}

export async function countMediaUsageReferences(assetId: string) {
  return db.$count(mediaUsage, eq(mediaUsage.assetId, assetId));
}
