import { NextResponse } from 'next/server';
import { eq } from 'drizzle-orm';

import { withAuth } from '@/lib/api/helpers';
import { db } from '@/lib/db';
import { ports } from '@/lib/db/schema/ports';
import { userProfiles } from '@/lib/db/schema/users';
import { deleteFile, uploadFile } from '@/lib/services/files';
import { errorResponse, ValidationError } from '@/lib/errors';
import { logger } from '@/lib/logger';

const MAX_AVATAR_BYTES = 2 * 1024 * 1024;

/**
 * Profile-photo upload. Accepts a multipart `file` (cropped JPEG/PNG
 * from the ImageCropperDialog), persists it via the polymorphic files
 * table (so an S3↔filesystem swap carries it correctly), and writes
 * the file id into `user_profiles.avatar_file_id`.
 *
 * Files are scoped to the user's CURRENT port - the rep can't end up
 * with an avatar that's only visible from one port. (Avatars render
 * via the GET handler below, which presigns by id regardless of port.)
 */
export const POST = withAuth(async (req, ctx) => {
  try {
    const formData = await req.formData();
    const fileEntry = formData.get('file');
    if (!(fileEntry instanceof File)) {
      throw new ValidationError('Missing `file` part');
    }
    if (fileEntry.size === 0) {
      throw new ValidationError('Empty file');
    }
    if (fileEntry.size > MAX_AVATAR_BYTES) {
      throw new ValidationError('Avatar exceeds 2 MB');
    }

    // Resolve the port slug for the storage path. Super-admins without
    // an active port fall through to a synthetic 'global' bucket.
    const port = ctx.portId
      ? await db.query.ports.findFirst({ where: eq(ports.id, ctx.portId) })
      : null;
    const portSlug = port?.slug ?? 'global';
    const portId = ctx.portId || port?.id || '';
    if (!portId) throw new ValidationError('No active port');

    const buffer = Buffer.from(await fileEntry.arrayBuffer());
    // Pick the storage filename's extension from the upload's MIME so
    // PNG uploads aren't silently relabelled `.jpg` (which would strip
    // the alpha-channel signal from the storage layer).
    const mimeType = fileEntry.type || 'image/jpeg';
    const ext =
      mimeType === 'image/png'
        ? 'png'
        : mimeType === 'image/webp'
          ? 'webp'
          : mimeType === 'image/gif'
            ? 'gif'
            : mimeType === 'image/avif'
              ? 'avif'
              : 'jpg';
    const record = await uploadFile(
      portId,
      portSlug,
      {
        buffer,
        originalName: fileEntry.name || `avatar.${ext}`,
        mimeType,
        size: fileEntry.size,
      },
      {
        filename: `avatar-${ctx.userId}.${ext}`,
        category: 'avatar',
        entityType: 'user',
        entityId: ctx.userId,
      },
      {
        userId: ctx.userId,
        portId,
        ipAddress: ctx.ipAddress,
        userAgent: ctx.userAgent,
      },
    );

    // file-lifecycle-auditor C1: capture the prior avatar id BEFORE
    // overwriting so we can clean it up. Without this every "Replace
    // photo" leaked one files row + one S3 blob, untethered (no
    // client/yacht/company FK) and invisible to UI sweeps.
    const prior = await db.query.userProfiles.findFirst({
      where: eq(userProfiles.userId, ctx.userId),
      columns: { avatarFileId: true },
    });
    const priorAvatarId = prior?.avatarFileId ?? null;

    await db
      .update(userProfiles)
      .set({ avatarFileId: record.id, updatedAt: new Date() })
      .where(eq(userProfiles.userId, ctx.userId));

    if (priorAvatarId && priorAvatarId !== record.id) {
      // Best-effort delete - a stale-blob failure shouldn't fail the
      // new-avatar response. deleteFile handles ref-check + blob
      // delete + audit so a referenced file (somehow) is safe.
      try {
        await deleteFile(priorAvatarId, portId, {
          userId: ctx.userId,
          portId,
          ipAddress: ctx.ipAddress,
          userAgent: ctx.userAgent,
        });
      } catch (err) {
        logger.warn(
          { err, priorAvatarId, userId: ctx.userId },
          'avatar replace: failed to clean up prior avatar file - orphan blob possible',
        );
      }
    }

    return NextResponse.json({ data: { avatarFileId: record.id } });
  } catch (error) {
    return errorResponse(error);
  }
});
