// src/server/payments/mock-provider.ts
// MockPaymentProvider — provider-neutral in-DB payment simulator.
// No network. Deterministic per the mock_scenario persisted on the row each op touches.
// Subdir mock/ (read-scenario.ts, scenarios.ts, index.ts) stays until Phase 12 deletes it.

import { sql } from 'drizzle-orm';
import type { MultidealEnv } from '../env.js';
import { getDb } from '../db/client.js';
import { recordMockEvent } from '../db/queries/mock-payment-events.js';
import * as purchaseQueries from '../db/queries/purchases.js';
import * as vendorQueries from '../db/queries/vendors.js';
import { scenarioErrorCode, isMockScenario, type MockScenario } from './mock/scenarios.js';
import type {
  PaymentProvider,
  ChargeInput,
  ChargeOutcome,
  CheckInput,
  CardTokenOutcome,
  HoldInput,
  HoldOutcome,
  CaptureInput,
  ReleaseInput,
  RefundInput,
  RefundOutcome,
  ReconcileInput,
  ReconcileOutcome,
  OnboardInput,
  OnboardOutcome,
  OnboardingSessionInput,
  OnboardingSessionOutcome,
  ClientConfig,
  FinalizeInput,
  FinalizeOutcome,
  ChargeOk,
  PaymentFailure,
  EnsureCustomerInput,
} from './provider.js';
import { PaymentErrorCode } from './provider.js';
import { finalizeMockPurchase } from './finalize.js';
import { floorAgorotPercent } from '@/lib/money';

export class MockPaymentProvider implements PaymentProvider {
  constructor(private readonly env: MultidealEnv) {}

  async ensureCustomerId(_input: EnsureCustomerInput): Promise<null> {
    return null;
  }

  private async readScenario(
    table: 'group_reservations' | 'vendors',
    id: string,
  ): Promise<MockScenario> {
    const db = getDb({ DATABASE_URL: this.env.DATABASE_URL });
    const rows = await db.execute(
      sql`SELECT mock_scenario FROM ${sql.raw(table)} WHERE id = ${id} LIMIT 1`,
    );
    const raw = (rows.rows[0] as { mock_scenario?: string } | undefined)?.mock_scenario;
    return isMockScenario(raw) ? raw : 'success';
  }

  async charge(input: ChargeInput): Promise<ChargeOutcome> {
    const scenario = await this.readScenario('vendors', input.vendor.vendorId);
    const errCode = scenarioErrorCode(scenario);
    if (errCode) {
      await recordMockEvent(this.env.DATABASE_URL, {
        op: 'charge',
        orderLineId: input.purchaseId,
        scenario,
        outcomeCode: errCode,
      });
      return { ok: false, code: errCode, message: `mock charge: ${scenario}` };
    }
    const platformAgorot = floorAgorotPercent(input.totalAgorot, 10);
    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'charge',
      orderLineId: input.purchaseId,
      scenario,
      outcomeCode: 'OK',
    });
    return finalizeMockPurchase(this.env, {
      id: `mock_pay_${input.purchaseId}`,
      object: 'payment_intent',
      status: 'succeeded',
      amount: input.totalAgorot,
      amount_received: input.totalAgorot,
      application_fee_amount: platformAgorot,
      currency: 'ils',
      created: Math.floor(Date.now() / 1000),
      livemode: false,
      latest_charge: null,
      metadata: { purchaseId: input.purchaseId },
      transfer_data: { destination: input.vendor.providerAccountId },
      ...(input.customerId ? { customer: input.customerId } : {}),
    } as never);
  }

  async finalize(input: FinalizeInput): Promise<FinalizeOutcome> {
    // Idempotent — return same shape as charge() for the given payment ID
    return {
      ok: true,
      status: 'succeeded',
      providerPaymentId: input.providerPaymentId,
      vendorAgorot: 0,
      platformAgorot: 0,
      vendorTaxDocId: null,
      platformTaxDocId: null,
      vendorTaxDocPdfUrl: null,
      platformTaxDocPdfUrl: null,
    };
  }

  async checkCard(input: CheckInput): Promise<CardTokenOutcome> {
    // Scenario derives from the synthetic token: mock_tok_<scenario>.
    const scenario = this.scenarioFromToken(input.paymentMethodId);
    if (scenario === 'hold_reject_debit') {
      return {
        ok: false,
        code: PaymentErrorCode.HOLD_NOT_SUPPORTED,
        message: 'mock: debit card',
      };
    }
    const errCode = scenarioErrorCode(scenario);
    if (errCode && errCode !== PaymentErrorCode.HOLD_NOT_SUPPORTED) {
      return {
        ok: false,
        code: errCode,
        message: `mock checkCard: ${scenario}`,
      };
    }
    return {
      ok: true,
      providerCardToken: `mock_card_${scenario}`,
      expirationMonth: 12,
      expirationYear: 2031,
      brand: 'visa',
      last4: '4242',
    };
  }

  async createSetupIntent(
    _userId: string,
  ): Promise<
    { ok: true; clientSecret: string } | { ok: true; clientSecret: null } | PaymentFailure
  > {
    // Mock provider has no SetupIntent — AddCardForm renders MockCardForm directly
    return { ok: true as const, clientSecret: null };
  }

  async placeHold(input: HoldInput): Promise<HoldOutcome> {
    const scenario = await this.readScenario('group_reservations', input.reservationId);
    if (scenario === 'hold_reject_debit') {
      await recordMockEvent(this.env.DATABASE_URL, {
        op: 'placeHold',
        reservationId: input.reservationId,
        scenario,
        outcomeCode: PaymentErrorCode.HOLD_NOT_SUPPORTED,
      });
      return {
        ok: false,
        code: PaymentErrorCode.HOLD_NOT_SUPPORTED,
        message: 'mock: Shva 003 debit',
      };
    }
    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'placeHold',
      reservationId: input.reservationId,
      scenario,
      outcomeCode: 'OK',
    });
    const expiresAt = new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString();
    return {
      ok: true,
      providerHoldId: `mock_hold_${input.reservationId}`,
      expiresAt,
    };
  }

  async captureHold(input: CaptureInput): Promise<ChargeOk | PaymentFailure> {
    const scenario = await this.readScenario('group_reservations', input.reservationId);
    const db = getDb({ DATABASE_URL: this.env.DATABASE_URL });
    // Dedup: a ledger 'captureHold' row for this reservation = already captured.
    const prior = await db.execute(sql`
      SELECT 1 FROM mock_payment_events
      WHERE op = 'captureHold' AND reservation_id = ${input.reservationId} AND outcome_code = 'OK'
      LIMIT 1
    `);
    if (prior.rows.length > 0) {
      return {
        ok: false,
        code: PaymentErrorCode.DUPLICATE,
        message: 'mock: hold already captured',
      };
    }
    const errCode = scenarioErrorCode(scenario);
    if (errCode) {
      await recordMockEvent(this.env.DATABASE_URL, {
        op: 'captureHold',
        reservationId: input.reservationId,
        orderLineId: input.purchaseId,
        scenario,
        outcomeCode: errCode,
      });
      return {
        ok: false,
        code: errCode,
        message: `mock captureHold: ${scenario}`,
      };
    }
    const platformAgorot = floorAgorotPercent(input.totalAgorot, 10);
    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'captureHold',
      reservationId: input.reservationId,
      orderLineId: input.purchaseId,
      scenario,
      outcomeCode: 'OK',
    });
    return {
      ok: true,
      status: 'succeeded',
      providerPaymentId: `mock_pay_${input.purchaseId}`,
      vendorAgorot: input.totalAgorot - platformAgorot,
      platformAgorot,
      vendorTaxDocId: null,
      platformTaxDocId: null,
      vendorTaxDocPdfUrl: null,
      platformTaxDocPdfUrl: null,
    };
  }

  async releaseHold(input: ReleaseInput): Promise<void> {
    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'releaseHold',
      reservationId: input.reservationId,
      scenario: 'success',
      outcomeCode: 'OK',
    });
  }

  async reconcileHold(_input: { providerHoldId: string }): Promise<{ status: 'cancellable' }> {
    return { status: 'cancellable' };
  }

  async refund(input: RefundInput): Promise<RefundOutcome> {
    const db = getDb({ DATABASE_URL: this.env.DATABASE_URL });
    const purchase = await purchaseQueries.findById(db, input.purchaseId);
    const paidAgorot = purchase ? Math.round(parseFloat(purchase.amountPaid) * 100) : 0;
    const refundedAgorot = input.amountAgorot ?? paidAgorot;
    const providerRefundId = `mock_refund_${input.purchaseId}`;

    // Settle the refund intent — mirrors stripe-provider so order status
    // (refunded / partially_refunded) recomputes on the mock path too.
    await purchaseQueries.setPurchaseRefund(db, input.purchaseId, {
      refundId: providerRefundId,
      amount: refundedAgorot,
    });

    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'refund',
      orderLineId: input.purchaseId,
      scenario: 'success',
      outcomeCode: 'OK',
    });
    return {
      ok: true,
      purchaseId: input.purchaseId,
      refundedAgorot,
      providerRefundId,
    };
  }

  async reconcile(_input: ReconcileInput): Promise<ReconcileOutcome> {
    // T7: purchases table removed; reconcile now handled by order model webhook flow.
    return { checked: 0, resolved: 0, failed: 0 };
  }

  async onboardVendor(input: OnboardInput): Promise<OnboardOutcome> {
    const db = getDb({ DATABASE_URL: this.env.DATABASE_URL });
    const providerAccountId = `acct_mock_${input.vendorId}`;
    await vendorQueries.setVendorStripeAccount(db, input.vendorId, providerAccountId);
    await recordMockEvent(this.env.DATABASE_URL, {
      op: 'onboardVendor',
      vendorId: input.vendorId,
      scenario: 'success',
      outcomeCode: 'charges_enabled',
    });
    return {
      ok: true,
      state: 'account_created',
      providerAccountId,
      hostedOnboardingUrl: '/vendor/onboarding/done',
      chargesEnabled: false,
    };
  }

  async createOnboardingSession(input: OnboardingSessionInput): Promise<OnboardingSessionOutcome> {
    return {
      ok: true,
      clientSecret: 'mock_cs_acct_session_' + input.vendorId,
      stripeAccountId: 'acct_mock_' + input.vendorId.slice(0, 8),
    };
  }

  getClientConfig(): ClientConfig {
    return { provider: 'mock' };
  }

  private scenarioFromToken(token: string): MockScenario {
    const suffix = token.startsWith('mock_tok_') ? token.slice('mock_tok_'.length) : '';
    return isMockScenario(suffix) ? suffix : 'success';
  }
}
