import { eq, sql } from 'drizzle-orm'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { MockInvoiceProvider } from '@platform-modules/invoicing'
import { PaypalProvider, type PaypalCreds } from '@platform-modules/billing/paypal'
import type { MorningCredentials } from '@platform-modules/invoicing/morning'

import { createWebhookRoute } from './webhooks.js'
import {
  accounts,
  chargeIntents,
  invoiceReferences,
  plugins,
  pressZoneInitSql,
  pressZoneSchema,
  subscriptionPackages,
  subscriptions,
  walletBalances,
} from '../schema.js'
import type { SettlementInvoiceDb } from '../lib/billing-doc.js'
import { periodKey } from '../lib/wallet.js'

const ACCOUNT_ID = '00000000-0000-0000-0000-000000000001'
const SUBSCRIPTION_ID = '00000000-0000-0000-0000-000000000002'
const PAYPAL_SUBSCRIPTION_ID = 'I-SUBSCRIPTION-1'
const PLUGIN_KEY = 'translate'
const TIER_KEY = 'pro'
const PERIOD = '2026-07'
const CREDIT_ALLOCATION = 5000n

function createMorningCredentials(): MorningCredentials {
  return {
    apiUser: 'api-user@example.test',
    apiPass: 'super-secret',
    companyId: 'company-123',
  }
}

function textResponse(status: number, body: string): Response {
  return new Response(body, { status })
}

function createWebhookHeaders(overrides: Record<string, string> = {}): Headers {
  return new Headers({
    'content-type': 'application/json',
    'paypal-auth-algo': 'SHA256withRSA',
    'paypal-cert-url': 'https://api-m.paypal.com/v1/notifications/certs/CERT-1',
    'paypal-transmission-id': 'transmission-1',
    'paypal-transmission-sig': 'c2lnbmF0dXJl',
    'paypal-transmission-time': '2026-07-04T10:00:00Z',
    ...overrides,
  })
}

function createPaypalProvider(
  fetchImpl: typeof fetch,
  subtleOverrides: Partial<SubtleCrypto> = {},
): PaypalProvider {
  vi.stubGlobal('crypto', {
    subtle: {
      importKey: vi.fn(async () => ({ key: 'paypal-cert' } as unknown as CryptoKey)),
      verify: vi.fn(async () => true),
      ...subtleOverrides,
    } satisfies Partial<SubtleCrypto>,
  })

  return new PaypalProvider({
    clientId: 'client-id',
    clientSecret: 'client-secret',
    webhookId: 'wh_123',
    fetch: fetchImpl,
  } satisfies PaypalCreds)
}

async function createTestDb() {
  const provider = new MockInvoiceProvider()
  const db = Object.assign(createPgliteClient({ schema: pressZoneSchema }), {
    morningCredential: createMorningCredentials(),
    morningProvider: provider,
    invoiceDate: '2026-07-04',
    invoiceCurrency: 'ILS',
  }) as SettlementInvoiceDb

  for (const statement of pressZoneInitSql.split(';').map((part) => part.trim()).filter(Boolean)) {
    await db.execute(sql.raw(statement))
  }

  await db.insert(accounts).values({
    id: ACCOUNT_ID,
    slug: 'press-zone-acct',
    type: 'individual',
    status: 'active',
    createdAt: new Date('2026-07-01T00:00:00Z'),
  })
  await db.insert(plugins).values({
    key: PLUGIN_KEY,
    name: 'Translate',
    permissionCatalog: [],
    createdAt: new Date('2026-07-01T00:00:00Z'),
  })
  await db.insert(subscriptionPackages).values({
    pluginKey: PLUGIN_KEY,
    tierKey: TIER_KEY,
    currency: 'ILS',
    priceMinor: 12_000n,
    creditAllocation: CREDIT_ALLOCATION,
    seatsConfig: 5,
    paypalPlanId: 'P-PLAN-1',
    createdAt: new Date('2026-07-01T00:00:00Z'),
  })
  await db.insert(subscriptions).values({
    id: SUBSCRIPTION_ID,
    accountId: ACCOUNT_ID,
    pluginKey: PLUGIN_KEY,
    tierKey: TIER_KEY,
    status: 'active',
    currentPeriodStart: new Date('2026-06-01T00:00:00Z'),
    currentPeriodEnd: new Date('2026-06-30T23:59:59Z'),
    paypalSubscriptionId: PAYPAL_SUBSCRIPTION_ID,
    createdAt: new Date('2026-06-01T00:00:00Z'),
    updatedAt: new Date('2026-06-01T00:00:00Z'),
  })

  return { db, provider }
}

async function readWalletBalance(db: SettlementInvoiceDb, ownerId: string): Promise<bigint> {
  const [row] = await db
    .select({ balance: walletBalances.balance })
    .from(walletBalances)
    .where(eq(walletBalances.ownerId, ownerId))
    .limit(1)

  return row?.balance ?? 0n
}

async function countLedgerEntries(db: SettlementInvoiceDb): Promise<number> {
  const result = await db.execute(sql`SELECT COUNT(*)::int AS count FROM ledger_entries`)
  const rows =
    (Array.isArray(result)
      ? result
      : (result as { rows?: Array<{ count?: number | string | bigint }> }).rows) ?? []
  return Number(rows[0]?.count ?? 0)
}

async function countInvoiceReferences(db: SettlementInvoiceDb): Promise<number> {
  const result = await db.execute(sql`SELECT COUNT(*)::int AS count FROM invoice_references`)
  const rows =
    (Array.isArray(result)
      ? result
      : (result as { rows?: Array<{ count?: number | string | bigint }> }).rows) ?? []
  return Number(rows[0]?.count ?? 0)
}

async function countChargeIntents(db: SettlementInvoiceDb): Promise<number> {
  const result = await db.execute(sql`SELECT COUNT(*)::int AS count FROM charge_intents`)
  const rows =
    (Array.isArray(result)
      ? result
      : (result as { rows?: Array<{ count?: number | string | bigint }> }).rows) ?? []
  return Number(rows[0]?.count ?? 0)
}

async function readChargeIntent(
  db: SettlementInvoiceDb,
  chargeKey: string,
): Promise<{ status: string; settledAt: Date | null } | null> {
  const [row] = await db
    .select({
      status: chargeIntents.status,
      settledAt: chargeIntents.settledAt,
    })
    .from(chargeIntents)
    .where(eq(chargeIntents.chargeKey, chargeKey))
    .limit(1)

  return row ?? null
}

function settlementBody(eventId: string): string {
  return JSON.stringify({
    id: eventId,
    event_type: 'PAYMENT.SALE.COMPLETED',
    resource: {
      id: `SALE-${eventId}`,
      billing_agreement_id: PAYPAL_SUBSCRIPTION_ID,
      create_time: '2026-07-04T10:00:00Z',
      amount: { total: '120.00', currency: 'ILS' },
      billing_period: { start_date: '2026-07-01T00:00:00Z' },
    },
  })
}

function refundBody(eventId: string, refundId: string): string {
  return JSON.stringify({
    id: eventId,
    event_type: 'PAYMENT.SALE.REFUNDED',
    resource: {
      id: refundId,
      sale_id: 'SALE-SETTLED-1',
      billing_agreement_id: PAYPAL_SUBSCRIPTION_ID,
      update_time: '2026-07-04T11:00:00Z',
      amount: { total: '120.00', currency: 'ILS' },
      billing_period: { start_date: '2026-07-01T00:00:00Z' },
    },
  })
}

afterEach(() => {
  vi.restoreAllMocks()
  vi.unstubAllGlobals()
})

describe('createWebhookRoute', () => {
  it('seeds the next-period wallet once for a redelivered chargeKey even when PayPal event ids differ', async () => {
    const { db, provider } = await createTestDb()
    const paypal = createPaypalProvider(
      vi.fn(async () =>
        textResponse(
          200,
          ['-----BEGIN PUBLIC KEY-----', 'AA==', '-----END PUBLIC KEY-----'].join('\n'),
        ),
      ) as unknown as typeof fetch,
    )
    const route = createWebhookRoute({ db, paypalProvider: paypal })
    const walletOwnerId = periodKey(ACCOUNT_ID, PLUGIN_KEY, PERIOD)
    const chargeKey = `${PAYPAL_SUBSCRIPTION_ID}:${PERIOD}`

    const first = await route.request('http://press-zone.test/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders(),
      body: settlementBody('WH-SETTLEMENT-1'),
    })
    const second = await route.request('http://press-zone.test/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders({ 'paypal-transmission-id': 'transmission-2' }),
      body: settlementBody('WH-SETTLEMENT-2'),
    })

    expect(first.status).toBe(200)
    expect(second.status).toBe(200)
    expect(await readWalletBalance(db, walletOwnerId)).toBe(CREDIT_ALLOCATION)
    expect(await countLedgerEntries(db)).toBe(1)
    expect(await countChargeIntents(db)).toBe(1)
    expect(await countInvoiceReferences(db)).toBe(1)
    expect(await readChargeIntent(db, chargeKey)).toEqual({
      status: 'settled',
      settledAt: expect.any(Date),
    })
    expect(provider.issueCallCount.current).toBe(1)
    expect(provider.calls).toHaveLength(1)
    expect(provider.calls[0]?.spec.docType).toBe('invoice')
    expect(provider.calls[0]?.spec.idempotencyKey).toBe(`settlement:${chargeKey}`)
  })

  it('issues exactly one credit-note for a refund webhook', async () => {
    const { db, provider } = await createTestDb()
    const paypal = createPaypalProvider(
      vi.fn(async () =>
        textResponse(
          200,
          ['-----BEGIN PUBLIC KEY-----', 'AA==', '-----END PUBLIC KEY-----'].join('\n'),
        ),
      ) as unknown as typeof fetch,
    )
    const route = createWebhookRoute({ db, paypalProvider: paypal })

    await route.request('http://press-zone.test/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders(),
      body: settlementBody('WH-SETTLEMENT-1'),
    })

    const firstRefund = await route.request('http://press-zone.test/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders({ 'paypal-transmission-id': 'transmission-3' }),
      body: refundBody('WH-REFUND-1', 'RFND-1'),
    })
    const secondRefund = await route.request('http://press-zone.test/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders({ 'paypal-transmission-id': 'transmission-4' }),
      body: refundBody('WH-REFUND-2', 'RFND-1'),
    })

    expect(firstRefund.status).toBe(200)
    expect(secondRefund.status).toBe(200)
    expect(await countLedgerEntries(db)).toBe(2)
    expect(await countInvoiceReferences(db)).toBe(2)
    expect(await readChargeIntent(db, `${PAYPAL_SUBSCRIPTION_ID}:${PERIOD}`)).toEqual({
      status: 'settled',
      settledAt: expect.any(Date),
    })
    expect(provider.calls.map((call) => call.spec.docType)).toEqual(['invoice', 'credit_note'])
    expect(provider.issueCallCount.current).toBe(2)
  })
})
