import { randomUUID } from "node:crypto";
import { and, desc, eq } from "drizzle-orm";
import { getDb } from "@/core/db/client";
import { projectMedia, projects } from "@/core/db/schema";
import { getStorage } from "@/core/storage/storage";
import { getProjectForTenant } from "@/services/project-service";
import type { TenantContext } from "@/core/tenant/types";

const ALLOWED_MIME = new Set([
  "image/jpeg",
  "image/png",
  "image/webp",
  "image/avif",
  "image/gif",
  "application/pdf",
]);

const MAX_BYTES = 25 * 1024 * 1024; // 25MB

function fileTypeFromMime(mime: string): string {
  if (mime.startsWith("image/")) return "image";
  if (mime === "application/pdf") return "document";
  return "other";
}

/**
 * Validate upload before storage.
 * Never trust extension alone — MIME allowlist + size + ownership.
 */
export function validateUpload(input: {
  mimeType: string;
  size: number;
  fileName: string;
}): { ok: true } | { ok: false; error: string } {
  if (!ALLOWED_MIME.has(input.mimeType)) {
    return { ok: false, error: "File type not allowed" };
  }
  if (input.size <= 0 || input.size > MAX_BYTES) {
    return { ok: false, error: "File size exceeds limit" };
  }
  if (/\.(exe|sh|bat|cmd|js|mjs|php|html|htm|svg)$/i.test(input.fileName)) {
    return { ok: false, error: "Executable or script uploads are blocked" };
  }
  return { ok: true };
}

export async function attachProjectMedia(
  ctx: TenantContext,
  input: {
    projectId: string;
    fileName: string;
    mimeType: string;
    body: Buffer;
    altText?: string;
  },
) {
  const project = await getProjectForTenant(ctx, input.projectId);
  if (!project) {
    return { ok: false as const, error: "Project not found" };
  }

  const validation = validateUpload({
    mimeType: input.mimeType,
    size: input.body.byteLength,
    fileName: input.fileName,
  });
  if (!validation.ok) {
    return validation;
  }

  const assetId = randomUUID();
  const storage = getStorage();
  const uploaded = await storage.uploadAsset({
    tenantId: ctx.tenantId,
    projectId: input.projectId,
    assetId,
    body: input.body,
    mimeType: input.mimeType,
    fileName: input.fileName,
  });

  const db = getDb();
  const [media] = await db
    .insert(projectMedia)
    .values({
      tenantId: ctx.tenantId,
      projectId: input.projectId,
      storageKey: uploaded.storageKey,
      fileType: fileTypeFromMime(input.mimeType),
      mimeType: input.mimeType,
      fileSize: uploaded.size,
      altText: input.altText ?? null,
      sortOrder: 0,
    })
    .returning();

  return { ok: true as const, media };
}

export async function listTenantMedia(ctx: TenantContext, limit = 100) {
  const db = getDb();
  return db
    .select()
    .from(projectMedia)
    .where(eq(projectMedia.tenantId, ctx.tenantId))
    .orderBy(desc(projectMedia.createdAt))
    .limit(Math.min(limit, 200));
}

export async function getMediaForTenant(ctx: TenantContext, mediaId: string) {
  const db = getDb();
  const [row] = await db
    .select()
    .from(projectMedia)
    .where(
      and(eq(projectMedia.id, mediaId), eq(projectMedia.tenantId, ctx.tenantId)),
    )
    .limit(1);
  return row ?? null;
}

export async function updateProjectMediaAlt(
  ctx: TenantContext,
  mediaId: string,
  altText: string,
) {
  const existing = await getMediaForTenant(ctx, mediaId);
  if (!existing) return null;

  const db = getDb();
  const [updated] = await db
    .update(projectMedia)
    .set({ altText: altText.trim() || null, updatedAt: new Date() })
    .where(
      and(eq(projectMedia.id, mediaId), eq(projectMedia.tenantId, ctx.tenantId)),
    )
    .returning();

  return updated ?? null;
}

export async function deleteProjectMedia(
  ctx: TenantContext,
  mediaId: string,
) {
  const db = getDb();
  const [existing] = await db
    .select()
    .from(projectMedia)
    .where(
      and(eq(projectMedia.id, mediaId), eq(projectMedia.tenantId, ctx.tenantId)),
    )
    .limit(1);

  if (!existing) return null;

  const storage = getStorage();
  await storage.deleteAsset(existing.storageKey, ctx.tenantId);

  await db
    .delete(projectMedia)
    .where(
      and(eq(projectMedia.id, mediaId), eq(projectMedia.tenantId, ctx.tenantId)),
    );

  await db
    .update(projects)
    .set({ heroStorageKey: null, updatedAt: new Date() })
    .where(
      and(
        eq(projects.id, existing.projectId),
        eq(projects.tenantId, ctx.tenantId),
        eq(projects.heroStorageKey, existing.storageKey),
      ),
    );

  return existing;
}
