import { createDbService } from '@/server/services/db.js';
import type Stripe from 'stripe';
import type { MultidealEnv } from '../../env.js';
import type { ReconcileOutcome } from '../provider.js';
import { getStripe } from './client.js';
import type { TxDrizzleClient, DrizzleDb } from '../../db/client.js';
import { sql, eq } from 'drizzle-orm';
import { order, orderLine } from '../../db/schema.js';
import { failOrder, type OrdersSchema } from '@platform-modules/commerce-orders';
import type { Transaction } from '@platform-modules/db';
import { finalizePurchase } from '../finalize.js';
import * as purchaseQueries from '../../db/queries/purchases.js';
import { captureCaught } from '../../observability/capture.server.js';
import { asOrderId, asOrderLineId, toModuleRef } from '@/server/platform-seams/ids.js';

const ABANDONED_PI_HOURS = 24;
const ABANDONED_CART_HOURS = 48;
const CONCURRENCY_LIMIT = 5;

interface StalePurchaseRow {
  id: string;
  chargeRef: string | null;
  createdAt: Date;
}

export async function reconcilePendingPurchases(env: MultidealEnv): Promise<ReconcileOutcome> {
  const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
  const stripe = getStripe(env);
  let checked = 0;
  let resolved = 0;
  let failed = 0;

  const staleRows: StalePurchaseRow[] = await db
    .select({
      id: orderLine.id,
      chargeRef: order.chargeRef,
      createdAt: orderLine.createdAt,
    })
    .from(orderLine)
    .innerJoin(order, eq(order.id, orderLine.orderId))
    .where(
      sql`${order.status} IN ('pending', 'charging') AND ${orderLine.createdAt} < now() - interval '15 minutes'`,
    )
    .limit(200);

  const withPi = staleRows.filter((r) => r.chargeRef != null);
  const abandonedCarts = staleRows.filter(
    (r) =>
      r.chargeRef == null && r.createdAt.getTime() < Date.now() - ABANDONED_CART_HOURS * 3600_000,
  );

  for (const row of abandonedCarts) {
    const purchaseRef = toModuleRef(asOrderLineId(row.id));
    checked++;
    try {
      await purchaseQueries.markPurchaseFailed(db, purchaseRef);
      resolved++;
    } catch (err) {
      failed++;
      captureCaught(err, {
        scope: 'stripe.reconcile.abandonedCart',
        severity: 'error',
        extra: { purchaseId: purchaseRef },
      });
    }
  }

  for (let i = 0; i < withPi.length; i += CONCURRENCY_LIMIT) {
    const batch = withPi.slice(i, i + CONCURRENCY_LIMIT);
    const results = await Promise.allSettled(
      batch.map((row) => resolveWithStripe(env, db, stripe, row)),
    );
    for (const result of results) {
      checked++;
      if (result.status === 'fulfilled') {
        if (result.value === 'resolved') resolved++;
      } else {
        failed++;
      }
    }
  }

  return { checked, resolved, failed };
}

type ResolveResult = 'resolved' | 'skipped';

async function resolveWithStripe(
  env: MultidealEnv,
  db: DrizzleDb,
  stripe: Stripe,
  row: StalePurchaseRow,
): Promise<ResolveResult> {
  const pi = await stripe.paymentIntents.retrieve(row.chargeRef!);
  const ageMs = Date.now() - row.createdAt.getTime();
  const ageHours = ageMs / 3_600_000;

  switch (pi.status) {
    case 'succeeded': {
      await finalizePurchase(env, pi);
      return 'resolved';
    }

    case 'canceled': {
      const purchaseRef = toModuleRef(asOrderLineId(row.id));
      await purchaseQueries.markPurchaseFailed(db, purchaseRef);
      // T3: mirror FAILED status on order model (additive — T7 handles cutover).
      await (async () => {
        let orderId: string | undefined;
        if (pi.metadata?.reservationId) {
          orderId = pi.metadata.reservationId;
        } else {
          const [orderRow] = await db
            .select({ id: order.id })
            .from(orderLine)
            .innerJoin(order, eq(order.id, orderLine.orderId))
            .where(eq(orderLine.id, purchaseRef))
            .limit(1);
          orderId = orderRow?.id;
        }
        if (!orderId) return;
        await (db as unknown as TxDrizzleClient).transaction(async (tx) => {
          await failOrder(
            tx as unknown as Transaction<OrdersSchema>,
            toModuleRef(asOrderId(orderId)),
            'canceled',
          );
        });
      })().catch((err: unknown) => {
        captureCaught(err, {
          scope: 'stripe.reconcile.failOrder.canceled',
          severity: 'warning',
          extra: { purchaseId: purchaseRef, piId: pi.id },
        });
      });
      return 'resolved';
    }

    case 'requires_payment_method':
    case 'requires_action': {
      if (ageHours < ABANDONED_PI_HOURS) {
        return 'skipped';
      }
      try {
        await stripe.paymentIntents.cancel(pi.id);
      } catch (err) {
        const stripeErr = err as { code?: string; type?: string; message?: string };
        if (stripeErr.code === 'payment_intent_unexpected_state') {
          // Already canceled or succeeded — safe to swallow, proceed to mark failed
        } else if (
          stripeErr.type === 'StripeAuthenticationError' ||
          stripeErr.type === 'StripeConnectionError' ||
          stripeErr.type === 'StripeAPIError'
        ) {
          // Network / auth / Stripe 5xx — rethrow so the reconcile loop can record a failure
          throw err;
        } else {
          // Unexpected error — log + capture so we get Sentry observability, but
          // continue so the purchase row still gets marked failed.
          captureCaught(err, {
            scope: 'stripe.reconcile.cancel',
            severity: 'warning',
            extra: { purchaseId: toModuleRef(asOrderLineId(row.id)), piId: pi.id },
          });
        }
      }
      const purchaseRef = toModuleRef(asOrderLineId(row.id));
      await purchaseQueries.markPurchaseFailed(db, purchaseRef);
      // T3: mirror FAILED status on order model (additive — T7 handles cutover).
      await (async () => {
        let orderId: string | undefined;
        if (pi.metadata?.reservationId) {
          orderId = pi.metadata.reservationId;
        } else {
          const [orderRow] = await db
            .select({ id: order.id })
            .from(orderLine)
            .innerJoin(order, eq(order.id, orderLine.orderId))
            .where(eq(orderLine.id, purchaseRef))
            .limit(1);
          orderId = orderRow?.id;
        }
        if (!orderId) return;
        await (db as unknown as TxDrizzleClient).transaction(async (tx) => {
          await failOrder(
            tx as unknown as Transaction<OrdersSchema>,
            toModuleRef(asOrderId(orderId)),
            'abandoned',
          );
        });
      })().catch((err: unknown) => {
        captureCaught(err, {
          scope: 'stripe.reconcile.failOrder.abandoned',
          severity: 'warning',
          extra: { purchaseId: purchaseRef, piId: pi.id },
        });
      });
      return 'resolved';
    }

    case 'processing': {
      return 'skipped';
    }

    default: {
      captureCaught(new Error(`Unexpected Stripe payment intent status: ${pi.status}`), {
        scope: 'stripe.reconcile.unknownStatus',
        severity: 'warning',
        extra: {
          purchaseId: toModuleRef(asOrderLineId(row.id)),
          piId: pi.id,
          status: pi.status,
        },
      });
      return 'skipped';
    }
  }
}
