import { createDbService } from '@/server/services/db.js';
import React from 'react';
import { z } from 'zod';
import { formatAgorotPlain } from '@/lib/money';
import { sendEmail } from '../../../email/resend.js';
import { insertOutboxRow } from '../../../db/queries/outbox.js';
import * as purchaseQueries from '../../../db/queries/purchases.js';
import * as resolutionsQ from '../../../db/queries/case-resolutions.js';
import * as offersQ from '../../../db/queries/case-offers.js';
import { getPaymentProvider } from '../../../payments/get-provider.js';
import { settlePurchaseRefund } from '../../refund.js';
import { enqueueCaseEmail } from '../../../support/email/enqueue-case-email.js';
import { captureCaught } from '@/server/observability/capture.server.js';
import type { OutboxHandler } from '../types.js';
import type { MultidealEnv } from '../../../env.js';
import { asCaseId } from '../../../platform-seams/ids.js';

const MAX_REFUND_ATTEMPTS = 3;

const payloadSchema = z.looseObject({
  caseId: z.string(),
  purchaseId: z.string(),
  amountCents: z.number().positive(),
  outcome: z.enum(['refund_full', 'refund_partial']).optional(),
  executedBy: z.enum(['vendor_auto', 'customer_accept', 'ai_auto', 'human']).optional(),
  email: z
    .object({
      templateKey: z.enum(['support.case_resolved', 'support.customer_decided']),
      recipients: z.array(z.enum(['customer', 'vendor'])),
      params: z.record(z.string(), z.string()),
    })
    .nullable()
    .optional(),
  attempt: z.number().int().positive().optional(),
});

type Payload = z.infer<typeof payloadSchema>;
type RefundOutcome = NonNullable<Payload['outcome']>;

function isRefundOutcome(outcome: string): outcome is RefundOutcome {
  return outcome === 'refund_full' || outcome === 'refund_partial';
}

export async function processSupportRefundRetry(
  ctx: Parameters<typeof supportRefundRetry.handle>[0],
  payload: Payload,
  event: Parameters<typeof supportRefundRetry.handle>[2],
): Promise<void> {
  const attempt = payload.attempt ?? 1;

  if (attempt > MAX_REFUND_ATTEMPTS) {
    console.warn(
      JSON.stringify({
        event: 'outbox_support_refund_cap_exceeded',
        caseId: payload.caseId,
        purchaseId: payload.purchaseId,
        attempt,
        outboxId: event.id,
      }),
    );
    if (ctx.RESEND_API_KEY) {
      const db = createDbService({ DATABASE_URL: ctx.DATABASE_URL });
      const { getSystemConfig } = await import('../../../db/queries/system-config.js');
      const raw = await getSystemConfig(db, 'support_notification_emails');
      let siteEmails: string[];
      try {
        siteEmails = JSON.parse(raw) as string[];
      } catch (err) {
        captureCaught(err, {
          scope: 'server.workflows.outbox.support-refund-retry',
          severity: 'warning',
        });
        siteEmails = [];
      }
      if (siteEmails.length === 0 && ctx.RESEND_FROM_EMAIL) siteEmails = [ctx.RESEND_FROM_EMAIL];
      for (const to of siteEmails) {
        await sendEmail(
          {
            RESEND_API_KEY: ctx.RESEND_API_KEY,
            RESEND_FROM_EMAIL: ctx.RESEND_FROM_EMAIL ?? '',
          },
          {
            to,
            subject: `[Multideal] Refund retry cap exceeded — case ${payload.caseId}`,
            react: React.createElement(
              'div',
              null,
              `Refund cap exceeded for case ${payload.caseId} / purchase ${payload.purchaseId}. Manual action required.`,
            ),
          },
        ).catch((err) => {
          captureCaught(err, {
            scope: 'server.workflows.outbox.support-refund-retry',
            severity: 'info',
          });
        });
      }
    }
    return;
  }

  const db = createDbService({ DATABASE_URL: ctx.DATABASE_URL });

  const existingResolution = await resolutionsQ.findByCase(db, payload.caseId);
  if (existingResolution) {
    console.info(
      JSON.stringify({
        event: 'outbox_support_refund_retry_skip_existing_resolution',
        caseId: payload.caseId,
        purchaseId: payload.purchaseId,
        resolutionId: existingResolution.id,
        outboxId: event.id,
      }),
    );
    return;
  }

  let outcome: RefundOutcome | undefined = payload.outcome;
  let executedBy = payload.executedBy;
  if (!outcome || !executedBy) {
    const offers = await offersQ.listByCase(db, payload.caseId);
    const executedOffer = offers.find(
      (o) => o.status === 'accepted' || o.status === 'auto_executed',
    );
    if (!executedOffer) {
      console.warn(
        JSON.stringify({
          event: 'outbox_support_refund_retry_no_executed_offer',
          caseId: payload.caseId,
          purchaseId: payload.purchaseId,
          outboxId: event.id,
        }),
      );
      throw new Error(`No executed offer for case ${payload.caseId}`);
    }
    if (!isRefundOutcome(executedOffer.outcome)) {
      throw new Error(`Executed offer for case ${payload.caseId} is not refundable`);
    }
    outcome = executedOffer.outcome;
    executedBy =
      executedOffer.status === 'auto_executed'
        ? ('vendor_auto' as const)
        : ('customer_accept' as const);
  }

  try {
    await purchaseQueries.claimRefundIntent(db, {
      purchaseId: payload.purchaseId,
      refundAmountAgorot: payload.amountCents,
      refundType: 'full',
      initiatedBy: 'case_resolution',
    });
  } catch (err) {
    if (err instanceof purchaseQueries.AlreadyRefundedError) {
      console.warn(
        JSON.stringify({
          event: 'outbox_support_refund_retry_claim_rejected',
          caseId: payload.caseId,
          purchaseId: payload.purchaseId,
          attempt,
          outboxId: event.id,
        }),
      );
      throw err;
    }
    throw err;
  }

  try {
    const refundResult = await (
      await getPaymentProvider(ctx as unknown as MultidealEnv)
    ).refund({
      purchaseId: payload.purchaseId,
      amountAgorot: payload.amountCents,
      idempotencyKey: `refund:${event.id}`,
    });

    if (!refundResult.ok) {
      await purchaseQueries.releaseRefundClaim(db, payload.purchaseId);
      throw new Error(refundResult.message ?? 'Refund failed');
    }

    const purchase = await purchaseQueries.findById(db, payload.purchaseId);
    if (!purchase) {
      throw new Error(`Purchase ${payload.purchaseId} not found`);
    }

    await settlePurchaseRefund(
      { db },
      {
        purchase,
        refundAmountAgorot: refundResult.refundedAgorot,
        refundAmountIls: formatAgorotPlain(payload.amountCents),
        providerRefundId: refundResult.providerRefundId,
        refundReasonText: `Case resolution: ${outcome}`,
        refundType: 'full',
      },
    );

    await resolutionsQ.insert(db, {
      caseId: asCaseId(payload.caseId),
      outcome,
      amountCents: payload.amountCents,
      executedBy,
      providerRefundId: refundResult.providerRefundId,
    });
    if (payload.email) {
      await enqueueCaseEmail(ctx, db, {
        caseId: payload.caseId,
        templateKey: payload.email.templateKey,
        recipients: payload.email.recipients,
        params: payload.email.params,
      });
    }

    console.info(
      JSON.stringify({
        event: 'outbox_support_refund_retry_success',
        caseId: payload.caseId,
        purchaseId: payload.purchaseId,
        attempt,
        outboxId: event.id,
      }),
    );
  } catch (err) {
    const nextAttempt = attempt + 1;
    console.warn(
      JSON.stringify({
        event: 'outbox_support_refund_retry_failed',
        caseId: payload.caseId,
        attempt,
        nextAttempt,
        error: err instanceof Error ? err.message : String(err),
        outboxId: event.id,
      }),
    );

    if (nextAttempt <= MAX_REFUND_ATTEMPTS) {
      await insertOutboxRow(db, {
        aggregateType: 'case',
        aggregateId: payload.caseId,
        eventType: 'support.refund.retry',
        payload: {
          caseId: payload.caseId,
          purchaseId: payload.purchaseId,
          amountCents: payload.amountCents,
          outcome,
          executedBy,
          email: payload.email ?? null,
          attempt: nextAttempt,
        },
      });
    }

    throw err;
  }
}

export const supportRefundRetry: OutboxHandler<Payload> = {
  type: 'support.refund.retry',
  payloadSchema,
  async handle(ctx, payload, event) {
    await processSupportRefundRetry(ctx, payload, event);
  },
};
