import type { PaymentProvider } from '@/server/payments/provider.js';
import type { TxDrizzleClient } from '@/server/db/client.js';
import { formatAgorotPlain } from '@/lib/money.js';
import { asCaseId } from '@/server/platform-seams/ids.js';
import * as offerQueries from '@/server/db/queries/case-offers.js';
import * as resolutionQueries from '@/server/db/queries/case-resolutions.js';
import * as caseQueries from '@/server/db/queries/support-cases.js';
import * as transitionQueries from '@/server/db/queries/support-state-transitions.js';
import * as adminActionQueries from '@/server/db/queries/admin-actions.js';
import { executeRefund, RefundError } from '@/server/workflows/refund.js';

export type AiOfferApprovalErrorCode =
  | 'CASE_NOT_FOUND'
  | 'OFFER_NOT_FOUND'
  | 'OFFER_CASE_MISMATCH'
  | 'OFFER_NOT_AI_REFUND'
  | 'OFFER_INVALID_AMOUNT'
  | 'OFFER_NOT_PENDING'
  | 'CASE_NOT_IN_HUMAN_REVIEW'
  | 'CASE_ALREADY_RESOLVED';

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

type ApprovalDeps = {
  db: TxDrizzleClient;
  payments: () => Promise<PaymentProvider>;
};

type ApprovalResult =
  | {
      offerId: string;
      resolutionId: string;
      refundId: string;
      amountCents: number;
      replayed: boolean;
    }
  | {
      offerId: string;
      resolutionId: string;
      amountCents: number;
      replayed: true;
      inProgress: true;
    };

type RefundOffer = NonNullable<Awaited<ReturnType<typeof offerQueries.findById>>>;
type RefundCase = NonNullable<Awaited<ReturnType<typeof caseQueries.lockHumanReviewById>>> & {
  orderLineId: string;
};
type CaseResolution = NonNullable<Awaited<ReturnType<typeof resolutionQueries.findByCase>>>;

function assertMatchingResolution(resolution: CaseResolution, offer: RefundOffer): void {
  if (resolution.outcome !== offer.outcome || resolution.amountCents !== offer.amountCents) {
    throw new AiOfferApprovalError('CASE_ALREADY_RESOLVED', 'Case has a different resolution');
  }
}

function replayResult(resolution: CaseResolution, offer: RefundOffer): ApprovalResult {
  assertMatchingResolution(resolution, offer);
  if (!resolution.providerRefundId) {
    throw new AiOfferApprovalError('CASE_ALREADY_RESOLVED', 'Case refund is still processing');
  }
  return {
    offerId: offer.id,
    resolutionId: resolution.id,
    refundId: resolution.providerRefundId,
    amountCents: offer.amountCents!,
    replayed: true,
  };
}

function assertRefundOffer(caseId: string, offer: RefundOffer): void {
  if (offer.caseId !== caseId) {
    throw new AiOfferApprovalError('OFFER_CASE_MISMATCH', 'Offer does not belong to this case');
  }
  if (
    offer.offeredBy !== 'ai' ||
    (offer.outcome !== 'refund_full' && offer.outcome !== 'refund_partial')
  ) {
    throw new AiOfferApprovalError('OFFER_NOT_AI_REFUND', 'Offer is not an AI refund offer');
  }
  if (!Number.isSafeInteger(offer.amountCents) || offer.amountCents! <= 0) {
    throw new AiOfferApprovalError('OFFER_INVALID_AMOUNT', 'Offer amount is invalid');
  }
}

async function reserveApproval(
  deps: ApprovalDeps,
  input: { caseId: string; offerId: string; adminId: string },
): Promise<{
  caseRow: RefundCase;
  offer: RefundOffer;
  resolution: CaseResolution;
}> {
  return deps.db.transaction(async (tx) => {
    const lockedCase = await caseQueries.lockHumanReviewById(tx, input.caseId);
    if (!lockedCase) {
      throw new AiOfferApprovalError(
        'CASE_NOT_IN_HUMAN_REVIEW',
        'Case is no longer eligible for AI refund approval',
      );
    }
    if (!lockedCase.orderLineId) {
      throw new AiOfferApprovalError('CASE_NOT_FOUND', 'Case purchase not found');
    }
    const caseRow: RefundCase = { ...lockedCase, orderLineId: lockedCase.orderLineId };

    const offer = await offerQueries.findById(tx, input.offerId);
    if (!offer) {
      throw new AiOfferApprovalError('OFFER_NOT_FOUND', 'Offer not found');
    }
    assertRefundOffer(input.caseId, offer);

    if (offer.status === 'pending') {
      const accepted = await offerQueries.tryAcceptAiRefund(tx, {
        id: input.offerId,
        caseId: input.caseId,
      });
      if (!accepted) {
        throw new AiOfferApprovalError('OFFER_NOT_PENDING', 'Offer is no longer pending');
      }
      await adminActionQueries.recordAdminAction(tx, {
        adminId: input.adminId,
        targetType: 'PURCHASE',
        targetId: caseRow.orderLineId,
        action: 'APPROVE',
        note: JSON.stringify({
          type: 'ai_case_offer',
          caseId: input.caseId,
          offerId: input.offerId,
        }),
      });
      offer.status = accepted.status;
      offer.decidedAt = accepted.decidedAt;
    }

    if (offer.status !== 'accepted') {
      throw new AiOfferApprovalError('OFFER_NOT_PENDING', 'Offer is no longer pending');
    }

    const existing = await resolutionQueries.findByCase(tx, input.caseId);
    if (existing) {
      assertMatchingResolution(existing, offer);
      return { caseRow, offer, resolution: existing };
    }

    const inserted = await resolutionQueries.insertIfAbsent(tx, {
      caseId: asCaseId(input.caseId),
      outcome: offer.outcome,
      amountCents: offer.amountCents,
      executedBy: 'human',
      providerRefundId: null,
    });
    if (inserted) return { caseRow, offer, resolution: inserted };

    const raced = await resolutionQueries.findByCase(tx, input.caseId);
    if (!raced) {
      throw new AiOfferApprovalError(
        'CASE_ALREADY_RESOLVED',
        'Case refund reservation was not persisted',
      );
    }
    assertMatchingResolution(raced, offer);
    return { caseRow, offer, resolution: raced };
  });
}

export async function approveAiRefundOffer(
  deps: ApprovalDeps,
  input: { caseId: string; offerId: string; adminId: string },
): Promise<ApprovalResult> {
  const caseRow = await caseQueries.findById(deps.db, input.caseId);
  if (!caseRow) {
    throw new AiOfferApprovalError('CASE_NOT_FOUND', 'Case not found');
  }
  const reservation = await reserveApproval(deps, input);
  if (reservation.resolution.providerRefundId) {
    return replayResult(reservation.resolution, reservation.offer);
  }

  let refund: Awaited<ReturnType<typeof executeRefund>>;
  try {
    refund = await executeRefund(
      { db: deps.db, payments: deps.payments },
      {
        purchaseId: reservation.caseRow.orderLineId,
        amount: formatAgorotPlain(reservation.offer.amountCents!),
        refundType: 'admin',
        initiatedBy: input.adminId,
        refundEventId: `case-offer:${reservation.offer.id}`,
      },
    );
  } catch (error) {
    if (error instanceof RefundError && error.code === 'REFUND_IN_PROGRESS') {
      return {
        offerId: reservation.offer.id,
        resolutionId: reservation.resolution.id,
        amountCents: reservation.offer.amountCents!,
        replayed: true,
        inProgress: true,
      };
    }

    const code = error instanceof RefundError ? error.code : 'INTERNAL_ERROR';
    const retryable = error instanceof RefundError ? error.retryable : true;
    await deps.db.transaction(async (tx) => {
      const current = await caseQueries.findById(tx, reservation.caseRow.id);
      if (!current || current.status === 'closed' || current.status === 'resolved') return;

      const transitioned = await caseQueries.transitionStatusIfCurrent(tx, {
        id: current.id,
        expectedStatus: current.status,
        status: 'human_review',
      });
      if (!transitioned) return;

      await transitionQueries.insert(tx, {
        parentType: 'case',
        parentId: current.id,
        fromState: current.status,
        toState: 'human_review',
        actorType: 'human_agent',
        actorId: input.adminId,
        reason: `ai_offer_refund_failed:${reservation.offer.id}:${code}:${retryable ? 'retryable' : 'terminal'}`,
      });
      await adminActionQueries.recordAdminAction(tx, {
        adminId: input.adminId,
        targetType: 'PURCHASE',
        targetId: reservation.caseRow.orderLineId,
        action: 'REQUEST_REFUND',
        note: JSON.stringify({
          type: 'ai_case_offer_execution_failed',
          caseId: current.id,
          offerId: reservation.offer.id,
          code,
        }),
      });
    });
    throw error;
  }

  const resolution = await deps.db.transaction(async (tx) => {
    const current = await resolutionQueries.findByCase(tx, reservation.caseRow.id);
    if (!current) {
      throw new AiOfferApprovalError('CASE_ALREADY_RESOLVED', 'Case refund reservation is missing');
    }
    assertMatchingResolution(current, reservation.offer);

    if (current.providerRefundId) {
      return current;
    }

    const completed = await resolutionQueries.completeProviderRefund(tx, {
      caseId: reservation.caseRow.id,
      providerRefundId: refund.refundId,
    });
    if (!completed) {
      throw new AiOfferApprovalError(
        'CASE_ALREADY_RESOLVED',
        'Case refund completion was not persisted',
      );
    }

    const currentCase = await caseQueries.findById(tx, reservation.caseRow.id);
    if (currentCase && currentCase.status !== 'closed' && currentCase.status !== 'resolved') {
      const transitioned = await caseQueries.transitionStatusIfCurrent(tx, {
        id: currentCase.id,
        expectedStatus: currentCase.status,
        status: 'resolved',
        extras: { resolvedAt: new Date() },
      });
      if (transitioned) {
        await transitionQueries.insert(tx, {
          parentType: 'case',
          parentId: currentCase.id,
          fromState: currentCase.status,
          toState: 'resolved',
          actorType: 'human_agent',
          actorId: input.adminId,
          reason: `ai_offer_refund_completed:${reservation.offer.id}`,
        });
      }
    }
    await adminActionQueries.recordAdminAction(tx, {
      adminId: input.adminId,
      targetType: 'PURCHASE',
      targetId: reservation.caseRow.orderLineId,
      action: 'REQUEST_REFUND',
      note: JSON.stringify({
        type: 'ai_case_offer_execution_completed',
        caseId: reservation.caseRow.id,
        offerId: reservation.offer.id,
        amountCents: reservation.offer.amountCents,
      }),
    });
    return completed;
  });

  return {
    offerId: reservation.offer.id,
    resolutionId: resolution.id,
    refundId: resolution.providerRefundId ?? refund.refundId,
    amountCents: reservation.offer.amountCents!,
    replayed: false,
  };
}
