/**
 * Group deal queries - typed async functions for the group_deals table.
 *
 * Entity-alarm law (§7): every create/update with a deadline arms the DO alarm;
 * every cancel/fail disarms it. Helpers from @/server/do-client are used.
 */

import { eq, and, inArray, lt, lte, sql, count } from 'drizzle-orm';
import type { DrizzleClient } from '../client.js';
import { groupDeals, deals, vendors } from '../schema.js';
import type { groupDealStateEnum } from '../schema.js';
import type { ServiceBundle } from '@/server/services/types.js';
import { GROUP_DEAL_STATE } from '@/lib/enums/group-deal-state';
import { armGroupDealAlarm, disarmGroupDealAlarm } from '@/server/do-client.js';
import { env } from '@/server/env.js';
import { captureCaught } from '@/server/observability/capture.server.js';

type GroupDealState = (typeof groupDealStateEnum.enumValues)[number];

// ─── Read helpers ─────────────────────────────────────────────────────────────

export async function findById(db: DrizzleClient, groupDealId: string) {
  const [row] = await db.select().from(groupDeals).where(eq(groupDeals.id, groupDealId)).limit(1);
  return row ?? null;
}

export async function findByDealId(db: DrizzleClient, dealId: string) {
  const [row] = await db.select().from(groupDeals).where(eq(groupDeals.dealId, dealId)).limit(1);
  return row ?? null;
}

/**
 * Returns active group deals (COLLECTING or THRESHOLD_MET) joined with their parent deal.
 */
export async function findActiveGroupDeals(
  db: DrizzleClient,
  opts: { limit?: number; offset?: number } = {},
) {
  const limit = opts.limit ?? 20;
  const offset = opts.offset ?? 0;

  return db
    .select({
      groupDeal: groupDeals,
      deal: deals,
    })
    .from(groupDeals)
    .innerJoin(deals, eq(groupDeals.dealId, deals.id))
    .where(inArray(groupDeals.groupState, ['COLLECTING', 'THRESHOLD_MET']))
    .limit(limit)
    .offset(offset);
}

/**
 * Returns group deals belonging to a vendor, joined with their parent deal.
 */
export async function findByVendorId(db: DrizzleClient, vendorId: string) {
  return db
    .select({
      groupDeal: groupDeals,
      deal: deals,
    })
    .from(groupDeals)
    .innerJoin(deals, eq(groupDeals.dealId, deals.id))
    .where(eq(deals.vendorId, vendorId));
}

// ─── State mutations ──────────────────────────────────────────────────────────

export async function updateGroupState(
  db: DrizzleClient,
  groupDealId: string,
  newState: GroupDealState,
  extraFields?: Partial<typeof groupDeals.$inferInsert>,
) {
  const [row] = await db
    .update(groupDeals)
    .set({ groupState: newState, ...extraFields })
    .where(eq(groupDeals.id, groupDealId))
    .returning();
  return row ?? null;
}

/**
 * Enter PARTIAL_PENDING — arms the partial_decision alarm at partialDecisionDeadline.
 */
export async function enterPartialPending(
  db: DrizzleClient,
  _services: Pick<ServiceBundle, 'doClient'>,
  groupDealId: string,
  partialDecisionDeadline: Date,
) {
  const [row] = await db
    .update(groupDeals)
    .set({ groupState: 'PARTIAL_PENDING', partialDecisionDeadline })
    .where(eq(groupDeals.id, groupDealId))
    .returning();
  if (row) {
    try {
      await armGroupDealAlarm(env, groupDealId, partialDecisionDeadline, 'partial_decision');
    } catch (err) {
      captureCaught(err, { scope: 'armGroupDealAlarm.partial', extra: { groupDealId } });
    }
  }
  return row ?? null;
}

/**
 * Fail / cancel a group deal — disarm all alarms.
 */
export async function failGroupDealRecord(
  db: DrizzleClient,
  _services: Pick<ServiceBundle, 'doClient'>,
  groupDealId: string,
) {
  const [row] = await db
    .update(groupDeals)
    .set({ groupState: 'FAILED', failedAt: new Date() })
    .where(eq(groupDeals.id, groupDealId))
    .returning();
  if (row) {
    try {
      await disarmGroupDealAlarm(env, groupDealId);
    } catch (err) {
      captureCaught(err, { scope: 'disarmGroupDealAlarm', extra: { groupDealId } });
    }
  }
  return row ?? null;
}

// ─── Atomic counter updates ───────────────────────────────────────────────────

/**
 * Atomically increments currentReservationCount by quantity.
 * Returns the updated row.
 */
export async function incrementReservationCount(
  db: DrizzleClient,
  groupDealId: string,
  quantity: number,
) {
  const [row] = await db
    .update(groupDeals)
    .set({
      currentReservationCount: sql`${groupDeals.currentReservationCount} + ${quantity}`,
    })
    .where(eq(groupDeals.id, groupDealId))
    .returning();
  return row ?? null;
}

/**
 * Atomically decrements currentReservationCount by quantity.
 * Clamps the counter to a minimum of 0 to prevent negative values.
 * Returns the updated row.
 */
export async function decrementReservationCount(
  db: DrizzleClient,
  groupDealId: string,
  quantity: number,
) {
  const [row] = await db
    .update(groupDeals)
    .set({
      currentReservationCount: sql`GREATEST(${groupDeals.currentReservationCount} - ${quantity}, 0)`,
    })
    .where(eq(groupDeals.id, groupDealId))
    .returning();
  return row ?? null;
}

// ─── Cron / expiry helpers ────────────────────────────────────────────────────

/**
 * Returns group deals whose deadline (deals.windowEnd) has passed and are still COLLECTING, THRESHOLD_MET, or EXTENDED.
 * Used by the cron job to trigger deadline evaluation.
 */
export async function findExpiredDeadlines(db: DrizzleClient) {
  const now = new Date();
  return db
    .select({
      groupDeal: groupDeals,
      deal: deals,
    })
    .from(groupDeals)
    .innerJoin(deals, eq(groupDeals.dealId, deals.id))
    .where(
      and(
        inArray(groupDeals.groupState, ['COLLECTING', 'THRESHOLD_MET', 'EXTENDED']),
        lt(deals.windowEnd, now),
      ),
    );
}

/**
 * Returns PARTIAL_PENDING group deals whose partialDecisionDeadline has passed.
 * Used by the cron job to auto-fail group deals not honored by vendor.
 */
export async function findPartialPendingExpired(db: DrizzleClient) {
  const now = new Date();
  return db
    .select({
      groupDeal: groupDeals,
      deal: deals,
    })
    .from(groupDeals)
    .innerJoin(deals, eq(groupDeals.dealId, deals.id))
    .where(
      and(
        eq(groupDeals.groupState, 'PARTIAL_PENDING'),
        lt(groupDeals.partialDecisionDeadline, now),
      ),
    );
}

/**
 * Returns HELD reservations whose holdExpiresAt is within 5 days from now.
 * Used by the cron job to refresh expiring holds (J5 refresh).
 * Returns group_deals rows joined with their parent deal whose reservations need refreshing.
 */
export async function findHoldsNeedingRefresh(db: DrizzleClient) {
  const fiveDaysFromNow = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
  return db
    .select({
      groupDeal: groupDeals,
      deal: deals,
    })
    .from(groupDeals)
    .innerJoin(deals, eq(groupDeals.dealId, deals.id))
    .where(
      and(
        inArray(groupDeals.groupState, ['COLLECTING', 'THRESHOLD_MET', 'EXTENDED']),
        lte(deals.windowEnd, fiveDaysFromNow),
      ),
    );
}

// ─── Admin list ───────────────────────────────────────────────────────────────

export interface GroupDealAdminRow {
  id: string;
  groupDealId: string;
  dealId: string;
  dealTitle: string;
  vendorId: string;
  vendorBusinessName: string;
  groupState: GroupDealState;
  currentReservationCount: number;
  minGroupSize: number;
  maxGroupSize: number;
  windowEnd: Date | null;
  createdAt: Date;
}

/**
 * Lists group deals for admin oversight, joined with deal + vendor.
 * Returns all states by default; pass state to filter.
 */
export async function listGroupDeals(
  db: DrizzleClient,
  opts: {
    state?: GroupDealState | 'all';
    limit?: number;
    offset?: number;
  } = {},
): Promise<{ data: GroupDealAdminRow[]; total: number }> {
  const limit = opts.limit ?? 20;
  const offset = opts.offset ?? 0;
  const activeStates: GroupDealState[] = [...GROUP_DEAL_STATE];
  const stateFilter =
    opts.state && opts.state !== 'all'
      ? eq(groupDeals.groupState, opts.state)
      : inArray(groupDeals.groupState, activeStates);

  const [rows, countRows] = await Promise.all([
    db
      .select({
        id: groupDeals.id,
        groupDealId: groupDeals.id,
        dealId: deals.id,
        dealTitle: deals.title,
        vendorId: vendors.id,
        vendorBusinessName: vendors.businessName,
        groupState: groupDeals.groupState,
        currentReservationCount: groupDeals.currentReservationCount,
        minGroupSize: groupDeals.minGroupSize,
        maxGroupSize: groupDeals.maxGroupSize,
        windowEnd: deals.windowEnd,
        createdAt: groupDeals.createdAt,
      })
      .from(groupDeals)
      .innerJoin(deals, eq(groupDeals.dealId, deals.id))
      .innerJoin(vendors, eq(deals.vendorId, vendors.id))
      .where(stateFilter)
      .orderBy(groupDeals.createdAt)
      .limit(limit)
      .offset(offset),
    db
      .select({ value: count() })
      .from(groupDeals)
      .innerJoin(deals, eq(groupDeals.dealId, deals.id))
      .where(stateFilter),
  ]);

  const total = countRows[0]?.value ?? 0;
  return { data: rows as GroupDealAdminRow[], total: Number(total) };
}
