/**
 * Personal Deal effect interpreter.
 *
 * Only place where DB writes + push are invoked for the personal-deal aggregate.
 *
 * Ordering (matches machine.ts emission order):
 *   1. create-request → returns row + arms DO alarm
 *   2. create-personal-deal → returns new ACTIVE deal row, arms its alarm
 *   3. mark-accepted | mark-rejected | mark-expired → disarms request alarm
 *   4. send-vendor-push / send-user-push (uses ids resolved in step 1/2)
 */

import { and, eq, isNull } from 'drizzle-orm';
import type { DrizzleClient } from '@/server/db/client.js';
import type { PushClient } from '@/server/push/types.js';
import type { DoClient } from '@/server/services/types.js';
import * as dealQueries from '@/server/db/queries/deals.js';
import * as personalDealRequestQueries from '@/server/db/queries/personal-deal-requests.js';
import { dealImages, deals, dealTranslations } from '@/server/db/schema.js';
import { acceptPersonalDealNewDealInputSchema } from '@/server/schemas/personal-deal.js';
import { createDealWithSkus } from '@/server/domain/vendor-deal/create-deal.js';
import type { PersonalDealEffect } from './effects.js';
import { enqueueDealTranslation } from '@/server/translation/jobs/enqueue.js';
import { he } from '@/lib/i18n/he.js';
import { captureCaught } from '@/server/observability/capture.server.js';

export interface ApplyPersonalDealDeps {
  db: DrizzleClient;
  push: PushClient;
  doClient: DoClient;
}

export interface ApplyPersonalDealResult {
  requestId?: string;
  request?: Awaited<ReturnType<typeof personalDealRequestQueries.createPersonalDealRequest>>;
  dealId?: string;
  deal?: NonNullable<Awaited<ReturnType<typeof dealQueries.findById>>>;
  updatedRequest?: Awaited<ReturnType<typeof personalDealRequestQueries.acceptPersonalDealRequest>>;
}

export async function applyEffects(
  deps: ApplyPersonalDealDeps,
  effects: PersonalDealEffect[],
): Promise<ApplyPersonalDealResult> {
  const { db, doClient } = deps;
  const result: ApplyPersonalDealResult = {};
  const pushT = he.vendor_personal_deals;

  for (const eff of effects) {
    switch (eff.kind) {
      case 'create-request': {
        const request = await personalDealRequestQueries.createPersonalDealRequest(
          db,
          { doClient },
          {
            userId: eff.userId,
            vendorId: eff.vendorId,
            sourceDealId: eff.sourceDealId,
            responseDeadline: eff.responseDeadline,
          },
        );
        result.request = request;
        result.requestId = request?.id;
        break;
      }
      case 'create-personal-deal': {
        const parsed = acceptPersonalDealNewDealInputSchema.parse(eff.input);
        const windowEnd = parsed.windowEnd ? new Date(parsed.windowEnd) : undefined;
        const sourceImages = await db
          .select({
            url: dealImages.url,
            isPrimary: dealImages.isPrimary,
            sortOrder: dealImages.sortOrder,
          })
          .from(dealImages)
          .where(and(eq(dealImages.dealId, eff.sourceDealId), isNull(dealImages.skuId)));
        if (sourceImages.length === 0) {
          throw new Error(`Source deal ${eff.sourceDealId} has no clonable images`);
        }
        const { dealId: newDealId } = await createDealWithSkus(db, {
          dealRow: {
            vendorId: eff.vendorId,
            dealType: parsed.dealType,
            title: parsed.title,
            description: parsed.description ?? '',
            categoryId: parsed.categoryId,
            windowStart: parsed.windowStart ? new Date(parsed.windowStart) : undefined,
            windowEnd,
            pickupStart: parsed.pickupStart,
            pickupEnd: parsed.pickupEnd,
            pickupAddress: parsed.pickupAddress ?? '',
            specialInstructions: parsed.specialInstructions,
            commissionRate: '0.050',
            isPersonalDeal: true,
            personalDealForUserId: eff.forUserId,
            dealState: 'DRAFT',
            isPhysical: true,
          },
          mode: 'single',
          single: {
            originalPrice: parsed.originalPrice,
            discountPercent: parsed.discountPercent,
            discountedPrice: parsed.discountedPrice,
            quantityTotal: parsed.quantityTotal,
          },
        });
        try {
          await db.insert(dealImages).values(
            sourceImages.map((image) => ({
              dealId: newDealId,
              ...image,
              approvalStatus: 'APPROVED' as const,
            })),
          );
          await db
            .update(deals)
            .set({ dealState: 'ACTIVE', approvedAt: new Date(), approvedBy: 'HUMAN' })
            .where(eq(deals.id, newDealId));
        } catch (error) {
          await db.delete(deals).where(eq(deals.id, newDealId));
          throw error;
        }
        // Arm expiry alarm for ACTIVE personal deals with a time window
        if (windowEnd) {
          try {
            await doClient.armDealAlarm(newDealId, windowEnd);
          } catch (err) {
            captureCaught(err, {
              scope: 'armDealAlarm.personalDeal',
              extra: { dealId: newDealId },
            });
          }
        }
        const newDeal = await dealQueries.findById(db, newDealId);
        if (!newDeal) throw new Error(`Personal deal ${newDealId} not found after create`);
        result.deal = newDeal;
        result.dealId = newDealId;
        await enqueueDealTranslation(db, newDealId).catch((e: unknown) =>
          console.error('[personal-deal] enqueueDealTranslation failed:', e),
        );
        break;
      }
      case 'mark-accepted': {
        if (!result.dealId) throw new Error('mark-accepted requires prior create-personal-deal');
        result.updatedRequest = await personalDealRequestQueries.acceptPersonalDealRequest(
          db,
          { doClient },
          eff.requestId,
          result.dealId,
        );
        break;
      }
      case 'mark-rejected': {
        result.updatedRequest = await personalDealRequestQueries.rejectPersonalDealRequest(
          db,
          { doClient },
          eff.requestId,
        );
        break;
      }
      case 'mark-expired': {
        await personalDealRequestQueries.expirePersonalDealRequest(db, { doClient }, eff.requestId);
        break;
      }
      case 'send-vendor-push': {
        if (eff.variant === 'request-created' && result.requestId) {
          await deps.push.sendToVendor(eff.vendorId, {
            title: pushT.push_vendor_request_created_title,
            body: pushT.push_vendor_request_created_body,
            url: `/vendor/personal-deals`,
            tag: `personal-deal-${result.requestId}`,
            data: { requestId: result.requestId, type: 'personal_deal_request' },
          });
        }
        break;
      }
      case 'send-user-push': {
        if (eff.variant === 'accepted' && result.dealId) {
          const [slugRow] = await deps.db
            .select({ slug: dealTranslations.slug })
            .from(dealTranslations)
            .where(
              and(eq(dealTranslations.dealId, result.dealId), eq(dealTranslations.locale, 'he')),
            )
            .limit(1);
          const dealUrl = slugRow?.slug ? `/deals/${slugRow.slug}` : '/deals';
          await deps.push.sendToUser(eff.userId, {
            title: pushT.push_accepted_title,
            body: pushT.push_accepted_body,
            url: dealUrl,
            tag: `personal-deal-ready-${result.dealId}`,
            data: { dealId: result.dealId, type: 'personal_deal_ready' },
          });
        } else if (eff.variant === 'rejected') {
          await deps.push.sendToUser(eff.userId, {
            title: pushT.push_rejected_title,
            body: pushT.push_rejected_body,
            tag: `personal-deal-rejected-${eff.contextId ?? ''}`,
            data: { requestId: eff.contextId, type: 'personal_deal_rejected' },
          });
        } else if (eff.variant === 'expired') {
          await deps.push.sendToUser(eff.userId, {
            title: pushT.push_expired_title,
            body: pushT.push_expired_body,
            tag: `personal-deal-expired-${eff.contextId ?? ''}`,
            data: { requestId: eff.contextId, type: 'personal_deal_expired' },
          });
        }
        break;
      }
    }
  }

  return result;
}
