import { NextResponse } from 'next/server';
import { z } from 'zod';
import { and, eq } from 'drizzle-orm';

import { withAuth, withRateLimit } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { db } from '@/lib/db';
import { berths, berthTags } from '@/lib/db/schema/berths';
import {
  archiveBerth,
  updateBerth,
  updateBerthStatus,
  setBerthTags,
} from '@/lib/services/berths.service';
import { errorResponse } from '@/lib/errors';

/**
 * Synchronous bulk endpoint for the berths list - mirrors the
 * /api/v1/interests/bulk shape so the rep-facing UX is consistent.
 *
 * Per-row loop with a 500-id cap. Bigger jobs belong on the BullMQ
 * `bulk` queue; the synchronous path gives reps an immediate per-row
 * failure list. Each row's mutation is independently transactional
 * (the underlying service helpers wrap their own writes).
 */
const bulkSchema = z.discriminatedUnion('action', [
  z.object({
    action: z.literal('change_status'),
    ids: z.array(z.string().min(1)).min(1).max(500),
    status: z.enum(['available', 'under_offer', 'sold']),
  }),
  z.object({
    action: z.literal('change_tenure_type'),
    ids: z.array(z.string().min(1)).min(1).max(500),
    tenureType: z.enum(['permanent', 'fixed_term']),
  }),
  z.object({
    action: z.literal('add_tag'),
    ids: z.array(z.string().min(1)).min(1).max(500),
    tagId: z.string().min(1),
  }),
  z.object({
    action: z.literal('remove_tag'),
    ids: z.array(z.string().min(1)).min(1).max(500),
    tagId: z.string().min(1),
  }),
  z.object({
    action: z.literal('archive'),
    ids: z.array(z.string().min(1)).min(1).max(500),
    reason: z.string().max(500).optional(),
  }),
]);

interface RowResult {
  id: string;
  ok: boolean;
  error?: string;
}

// Berths share a single `edit` permission for non-price mutations (no
// separate `archive` perm today - sales-manager + super-admin own all
// edit paths).
const PERMISSION_BY_ACTION: Record<
  z.infer<typeof bulkSchema>['action'],
  { resource: 'berths'; action: 'edit' }
> = {
  change_status: { resource: 'berths', action: 'edit' },
  change_tenure_type: { resource: 'berths', action: 'edit' },
  add_tag: { resource: 'berths', action: 'edit' },
  remove_tag: { resource: 'berths', action: 'edit' },
  archive: { resource: 'berths', action: 'edit' },
};

export const POST = withAuth(
  withRateLimit('bulk', async (req, ctx) => {
    let body: z.infer<typeof bulkSchema>;
    try {
      body = await parseBody(req, bulkSchema);
    } catch (error) {
      return errorResponse(error);
    }

    const perm = PERMISSION_BY_ACTION[body.action];
    const allowed = ctx.isSuperAdmin ? true : !!ctx.permissions?.[perm.resource]?.[perm.action];
    if (!allowed) {
      return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
    }

    const meta = {
      userId: ctx.userId,
      portId: ctx.portId,
      ipAddress: ctx.ipAddress,
      userAgent: ctx.userAgent,
    };

    const results: RowResult[] = [];

    for (const id of body.ids) {
      try {
        if (body.action === 'change_status') {
          // Status mutations go through the dedicated path so the under-
          // offer / sold transitions can auto-create the primary
          // interest_berths link + emit the rules-engine evaluation.
          await updateBerthStatus(
            id,
            ctx.portId,
            { status: body.status, reason: 'Bulk status change' },
            meta,
          );
        } else if (body.action === 'change_tenure_type') {
          await updateBerth(id, ctx.portId, { tenureType: body.tenureType }, meta);
        } else if (body.action === 'archive') {
          await archiveBerth(id, ctx.portId, { reason: body.reason ?? '' }, meta);
        } else if (body.action === 'add_tag' || body.action === 'remove_tag') {
          const berth = await db.query.berths.findFirst({
            where: and(eq(berths.id, id), eq(berths.portId, ctx.portId)),
          });
          if (!berth) {
            results.push({ id, ok: false, error: 'Not found' });
            continue;
          }
          // Compose the new tag set, then re-write atomically.
          const currentTags = await db
            .select({ tagId: berthTags.tagId })
            .from(berthTags)
            .where(eq(berthTags.berthId, id));
          const currentIds = new Set(currentTags.map((t) => t.tagId));
          if (body.action === 'add_tag') currentIds.add(body.tagId);
          else currentIds.delete(body.tagId);
          await setBerthTags(id, ctx.portId, Array.from(currentIds), meta);
        }
        results.push({ id, ok: true });
      } catch (err) {
        const message = err instanceof Error ? err.message : 'Unknown error';
        results.push({ id, ok: false, error: message });
      }
    }

    const okCount = results.filter((r) => r.ok).length;
    return NextResponse.json({
      data: {
        action: body.action,
        total: results.length,
        ok: okCount,
        failed: results.length - okCount,
        results,
      },
    });
  }),
);
