import { writeFile, unlink } from "fs/promises";
import path from "path";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
  canManageExperienciometro,
  createExperienceStoredFilename,
  ensureExperienceBaseline,
  ensureExperienceUploadDir,
  EXPERIENCE_MAX_UPLOAD_BYTES,
  EXPERIENCE_UPLOAD_DIR,
  experienceEvidenceKindFromMimeType,
  isAllowedExperienceMimeType,
  mapExperienceEvidence,
} from "@/lib/experienciometro";

function isUploadLikeFile(value: FormDataEntryValue | null): value is File {
  if (!value || typeof value === "string") {
    return false;
  }

  const candidate = value as Partial<File>;
  return typeof candidate.size === "number" && typeof candidate.type === "string" && typeof candidate.arrayBuffer === "function";
}

export async function POST(request: Request) {
  const session = await getServerSession(authOptions);
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (!canManageExperienciometro(session.user.role)) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  await ensureExperienceBaseline(prisma, session.user.id);

  const formData = await request.formData();
  const title = String(formData.get("title") ?? "").trim();
  const note = String(formData.get("note") ?? "").trim() || null;
  const locationId = String(formData.get("locationId") ?? "").trim();
  const evaluationId = String(formData.get("evaluationId") ?? "").trim() || null;
  const findingId = String(formData.get("findingId") ?? "").trim() || null;
  const responseId = String(formData.get("responseId") ?? "").trim() || null;
  const fileValue = formData.get("file");

  if (!title || !locationId) {
    return NextResponse.json({ error: "title and locationId are required." }, { status: 400 });
  }

  if (!isUploadLikeFile(fileValue)) {
    return NextResponse.json({ error: "file is required." }, { status: 400 });
  }

  if (fileValue.size <= 0) {
    return NextResponse.json({ error: "file is empty." }, { status: 400 });
  }

  if (fileValue.size > EXPERIENCE_MAX_UPLOAD_BYTES) {
    return NextResponse.json({ error: "file exceeds 10 MB limit." }, { status: 400 });
  }

  if (!isAllowedExperienceMimeType(fileValue.type)) {
    return NextResponse.json({ error: "Unsupported file type." }, { status: 400 });
  }

  const location = await prisma.location.findFirst({
    where: {
      id: locationId,
      isActive: true,
    },
    select: { id: true },
  });

  if (!location) {
    return NextResponse.json({ error: "Location not found." }, { status: 404 });
  }

  if (evaluationId) {
    const evaluation = await prisma.experienceEvaluation.findFirst({
      where: {
        id: evaluationId,
        locationId,
      },
      select: { id: true },
    });

    if (!evaluation) {
      return NextResponse.json({ error: "Invalid evaluationId for location." }, { status: 400 });
    }
  }

  if (findingId) {
    const finding = await prisma.experienceFinding.findFirst({
      where: {
        id: findingId,
        locationId,
      },
      select: { id: true },
    });

    if (!finding) {
      return NextResponse.json({ error: "Invalid findingId for location." }, { status: 400 });
    }
  }

  if (responseId) {
    const response = await prisma.experienceEvaluationResponse.findFirst({
      where: {
        id: responseId,
        evaluation: {
          locationId,
        },
      },
      select: { id: true },
    });

    if (!response) {
      return NextResponse.json({ error: "Invalid responseId for location." }, { status: 400 });
    }
  }

  await ensureExperienceUploadDir();
  const storagePath = createExperienceStoredFilename(fileValue.type);
  const absolutePath = path.join(EXPERIENCE_UPLOAD_DIR, storagePath);
  const bytes = Buffer.from(await fileValue.arrayBuffer());

  try {
    await writeFile(absolutePath, bytes);

    const evidence = await prisma.experienceEvidence.create({
      data: {
        locationId,
        evaluationId,
        findingId,
        responseId,
        uploadedById: session.user.id,
        kind: experienceEvidenceKindFromMimeType(fileValue.type),
        title,
        note,
        storagePath,
        mimeType: fileValue.type,
        sizeBytes: fileValue.size,
      },
      include: {
        uploadedBy: {
          select: {
            name: true,
            email: true,
          },
        },
      },
    });

    return NextResponse.json({
      ok: true,
      evidence: {
        ...mapExperienceEvidence(evidence),
        assetUrl: `/api/experienciometro/evidence/assets/${evidence.id}`,
      },
    });
  } catch (error) {
    await unlink(absolutePath).catch(() => {
      // ignore cleanup failure
    });

    const message = error instanceof Error ? error.message : "Failed to upload evidence.";
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
