import { sql } from 'drizzle-orm';
import type { DrizzleClient, TxDrizzleClient } from '@/server/db/client.js';
import type { PushClient } from '@/server/push/types.js';
import type { DoClient } from '@/server/services/types.js';
import type { PaymentProvider } from '@/server/payments/provider.js';
import * as groupReservationQueries from '@/server/db/queries/group-reservations.js';
import { env } from '@/server/env.js';
import { captureCaught } from '@/server/observability/capture.server.js';
import { enqueueOutbox } from '@/server/queues/outbox-producer.js';
import { insertOutboxRow } from '@/server/db/queries/outbox.js';
import type { GroupDealContext } from './machine.js';
import type { GroupDealEffect } from './effects.js';

export interface GroupDealDeps {
  db: TxDrizzleClient;
  push: PushClient;
  doClient: DoClient;
  payments: () => Promise<PaymentProvider>;
  qrSecret: string;
  storage: {
    bucket: R2Bucket;
    publicBase: string;
    uploadQr(purchaseId: string, token: string): Promise<string>;
  };
}

export class GroupDealError extends Error {
  constructor(
    public readonly code: string,
    message: string,
  ) {
    super(message);
    this.name = 'GroupDealError';
  }
}

export async function enqueueEvent(
  db: DrizzleClient,
  aggregateId: string,
  eventType: string,
  payload: Record<string, unknown>,
) {
  const { id: outboxId } = await insertOutboxRow(db, {
    aggregateType: 'group_deal',
    aggregateId,
    eventType,
    payload,
  });
  await enqueueOutbox(outboxId);
}

export async function safeArmPartialDecisionAlarm(
  doClient: DoClient,
  groupDealId: string,
  at: Date,
): Promise<void> {
  try {
    await doClient.armGroupDealAlarm(groupDealId, at, 'partial_decision');
  } catch (err) {
    captureCaught(err, { scope: 'armGroupDealAlarm.partial', extra: { groupDealId } });
  }
}

export async function safeDisarmGroupDealAlarm(
  doClient: DoClient,
  groupDealId: string,
): Promise<void> {
  try {
    await doClient.disarmGroupDealAlarm(groupDealId);
  } catch (err) {
    captureCaught(err, { scope: 'disarmGroupDealAlarm', extra: { groupDealId } });
  }
}

export async function safeArmGroupDealDeadlineAlarm(
  doClient: DoClient,
  groupDealId: string,
  at: Date,
): Promise<void> {
  try {
    await doClient.armGroupDealAlarm(groupDealId, at, 'deadline');
  } catch (err) {
    captureCaught(err, { scope: 'armGroupDealAlarm.deadline.extend', extra: { groupDealId } });
  }
}

export function withoutAlarmEffects(effects: GroupDealEffect[]): GroupDealEffect[] {
  return effects.filter((effect) => effect.kind !== 'arm-alarm' && effect.kind !== 'disarm-alarm');
}

export function buildCtx(
  groupDeal: {
    id: string;
    dealId: string;
    groupState: string;
    minGroupSize: number;
    currentReservationCount: number;
    extensionCount: number;
  },
  deal: { id: string; vendorId: string } | null,
): GroupDealContext {
  return {
    state: groupDeal.groupState as GroupDealContext['state'],
    groupDealId: groupDeal.id,
    dealId: groupDeal.dealId,
    vendorId: deal?.vendorId ?? '',
    minGroupSize: groupDeal.minGroupSize,
    currentReservationCount: groupDeal.currentReservationCount,
    extensionCount: groupDeal.extensionCount,
  };
}

export function makeApplyCtx(
  deps: GroupDealDeps,
  executeGroupDeal: (groupDealId: string) => Promise<unknown>,
) {
  return {
    db: deps.db,
    env,
    doClient: deps.doClient,
    executeGroupDeal,
    releaseGroupDealReservations: async (groupDealId: string) => {
      const heldReservations = await groupReservationQueries.findHeldByGroupDeal(deps.db, groupDealId);
      for (const reservation of heldReservations) {
        await groupReservationQueries.updateStatus(deps.db, reservation.id, 'RELEASED');
      }
    },
  };
}

export const sqlIncrement = sql;
