import { and, eq, isNull, sql } from 'drizzle-orm';
import { z } from 'zod';
import type { DrizzleClient } from '@/server/db/client';
import { adminActions, imageUploads, notifications, outbox, vendors } from '@/server/db/schema';
import { createImageUpload } from '@/server/db/queries/image-uploads';
import { resolveImageNotifications } from '@/server/db/queries/notifications';
import { deliverImageRejected } from '@/server/workflows/outbox/handlers/image.rejected';
import type { FactoryOperationHandler, FactoryStore } from './core';

const createUploadInput = z.object({
  key: z.uuid(),
  vendorId: z.uuid(),
  purpose: z.literal('vendor_logo'),
});
const processRejectionInput = z.object({ uploadId: z.uuid() });

export function createImageFactoryHandlers(input: {
  db: DrizzleClient;
  store: FactoryStore;
}): Record<string, FactoryOperationHandler> {
  return {
    createImageUpload: {
      dependencyOrder: 50,
      reserve: (raw) => ({ kind: 'image-upload', id: createUploadInput.parse(raw).key }),
      async execute({ runId, input: raw }) {
        const parsed = createUploadInput.parse(raw);
        if (!(await input.store.findOwned(runId, 'vendor', parsed.vendorId))) {
          throw new Error('Image uploader vendor is not owned by factory run');
        }
        const r2Key = `e2e/${runId}/vendor-logo/${parsed.key}.png`;
        await resolveImageNotifications(input.db, {
          entityType: 'vendor',
          entityId: parsed.vendorId,
          purpose: parsed.purpose,
        });
        await input.db
          .update(vendors)
          .set({
            pendingLogoUrl: `/r2/${r2Key}`,
            pendingLogoR2Key: r2Key,
            logoApprovalStatus: 'PENDING',
            logoRejectReasonCode: null,
          })
          .where(eq(vendors.id, parsed.vendorId));
        await createImageUpload(input.db, {
          id: parsed.key,
          uploaderVendorId: parsed.vendorId,
          r2Key,
          mime: 'image/png',
          sizeBytes: 68,
          scanStatus: 'PENDING',
          approvalStatus: 'PENDING',
          purpose: parsed.purpose,
          entityType: 'vendor',
          entityId: parsed.vendorId,
          aiDecision: 'FLAG',
          aiReason: 'Deterministic E2E moderation fixture',
          aiScore: '0.50',
          aiModel: 'e2e-fixture',
          aiCheckedAt: new Date(),
        });
        return {
          result: { uploadId: parsed.key, vendorId: parsed.vendorId, r2Key },
        };
      },
      async read({ entity }) {
        const [upload] = await input.db
          .select()
          .from(imageUploads)
          .where(eq(imageUploads.id, entity.entityId))
          .limit(1);
        if (!upload) throw new Error('Owned image upload missing');
        const [vendor, rejectionNotifications, rejectionEvents] = await Promise.all([
          input.db
            .select({
              id: vendors.id,
              logoApprovalStatus: vendors.logoApprovalStatus,
              pendingLogoUrl: vendors.pendingLogoUrl,
              pendingLogoR2Key: vendors.pendingLogoR2Key,
            })
            .from(vendors)
            .where(eq(vendors.id, upload.uploaderVendorId!))
            .limit(1),
          input.db
            .select()
            .from(notifications)
            .where(
              and(
                eq(notifications.eventType, 'image.rejected'),
                eq(notifications.recipientType, 'vendor'),
                eq(notifications.recipientId, upload.uploaderVendorId!),
                sql`${notifications.payload}->>'entityId' = ${upload.entityId}`,
                sql`${notifications.payload}->>'purpose' = ${upload.purpose}`,
              ),
            ),
          input.db
            .select()
            .from(outbox)
            .where(
              and(
                eq(outbox.eventType, 'image.rejected'),
                sql`${outbox.payload}->>'imageId' = ${entity.entityId}`,
              ),
            ),
        ]);
        return { upload, vendor: vendor[0] ?? null, rejectionNotifications, rejectionEvents };
      },
      async cleanup({ entity }) {
        await input.db
          .delete(notifications)
          .where(
            and(
              eq(notifications.eventType, 'image.rejected'),
              sql`${notifications.payload}->>'imageId' = ${entity.entityId}`,
            ),
          );
        await input.db
          .delete(outbox)
          .where(
            and(
              eq(outbox.eventType, 'image.rejected'),
              sql`${outbox.payload}->>'imageId' = ${entity.entityId}`,
            ),
          );
        await input.db.delete(adminActions).where(eq(adminActions.targetId, entity.entityId));
        const [upload] = await input.db
          .select({ vendorId: imageUploads.uploaderVendorId, r2Key: imageUploads.r2Key })
          .from(imageUploads)
          .where(eq(imageUploads.id, entity.entityId))
          .limit(1);
        await input.db.delete(imageUploads).where(eq(imageUploads.id, entity.entityId));
        if (upload?.vendorId) {
          await input.db
            .update(vendors)
            .set({ pendingLogoUrl: null, pendingLogoR2Key: null })
            .where(
              and(eq(vendors.id, upload.vendorId), eq(vendors.pendingLogoR2Key, upload.r2Key)),
            );
        }
      },
    },
    processImageRejection: {
      dependencyOrder: 0,
      async execute({ runId, input: raw }) {
        const { uploadId } = processRejectionInput.parse(raw);
        if (!(await input.store.findOwned(runId, 'image-upload', uploadId))) {
          throw new Error('Image upload is not owned by factory run');
        }
        const [event] = await input.db
          .select()
          .from(outbox)
          .where(
            and(
              eq(outbox.eventType, 'image.rejected'),
              sql`${outbox.payload}->>'imageId' = ${uploadId}`,
              isNull(outbox.processedAt),
            ),
          )
          .limit(1);
        if (!event) throw new Error('Owned image rejection event missing');
        const payload = z
          .object({
            imageId: z.uuid(),
            ownerType: z.enum(['user', 'vendor']),
            ownerId: z.uuid(),
            entityType: z.string().min(1),
            entityId: z.uuid(),
            purpose: z.string().min(1),
            reason: z.string().min(1),
          })
          .parse(event.payload);
        const [upload] = await input.db
          .select({ vendorId: imageUploads.uploaderVendorId, entityId: imageUploads.entityId })
          .from(imageUploads)
          .where(eq(imageUploads.id, uploadId))
          .limit(1);
        if (
          !upload?.vendorId ||
          event.aggregateType !== 'vendor' ||
          event.aggregateId !== upload.vendorId ||
          payload.imageId !== uploadId ||
          payload.ownerType !== 'vendor' ||
          payload.ownerId !== upload.vendorId ||
          payload.entityType !== 'vendor' ||
          payload.entityId !== upload.entityId ||
          payload.purpose !== 'vendor_logo'
        ) {
          throw new Error('Owned image rejection event contract mismatch');
        }
        await deliverImageRejected(input.db, payload, {
          id: event.id,
          aggregateType: event.aggregateType,
          aggregateId: event.aggregateId,
        });
        await input.db
          .update(outbox)
          .set({ processedAt: new Date() })
          .where(eq(outbox.id, event.id));
        return { result: { outboxId: event.id } };
      },
      async read() {
        throw new Error('Process command has no entity');
      },
      async cleanup() {},
    },
  };
}
