import { eq, sql } from 'drizzle-orm'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { MorningCredentials } from '../../../../packages/invoicing/src/morning/index.ts'
import { createPgliteClient } from '../../../../packages/db/src/postgres/pglite.ts'
import { MockInvoiceProvider } from '../../../../packages/invoicing/src/mock-provider.ts'
import {
  PaypalProvider,
  type PaypalCreds,
} from '../../../../packages/billing/src/paypal/index.ts'

import { createApp } from '../../src/index.js'
import { periodKey } from '../../src/lib/wallet.js'
import { createWebhookRoute } from '../../src/routes/webhooks.js'
import {
  accounts,
  chargeIntents,
  invoiceReferences,
  plugins,
  pressZoneInitSql,
  pressZoneSchema,
  subscriptionPackages,
  subscriptions,
  walletBalances,
} from '../../src/schema.js'
import type { SettlementInvoiceDb } from '../../src/lib/billing-doc.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): PaypalProvider {
  vi.stubGlobal('crypto', {
    subtle: {
      importKey: vi.fn(async () => ({ key: 'paypal-cert' } as unknown as CryptoKey)),
      verify: vi.fn(async () => true),
    } 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 countRows(
  db: SettlementInvoiceDb,
  table: 'ledger_entries' | 'invoice_references' | 'charge_intents' | 'wallet_balances',
  whereClause?: string,
): Promise<number> {
  const result = await db.execute(
    sql.raw(`SELECT COUNT(*)::int AS count FROM ${table}${whereClause ? ` ${whereClause}` : ''}`),
  )
  const rows =
    (Array.isArray(result)
      ? result
      : (result as { rows?: Array<{ count?: number | string | bigint }> }).rows) ?? []

  return Number(rows[0]?.count ?? 0)
}

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' },
    },
  })
}

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

describe('HTTP integration: webhook idempotency flow', () => {
  it('turns a double-delivered settlement into one wallet seed, one invoice, and one ledger entry', 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 app = createApp({
      webhookRoute: createWebhookRoute({ db, paypalProvider: paypal }),
    })
    const walletOwnerId = periodKey(ACCOUNT_ID, PLUGIN_KEY, PERIOD)

    const first = await app.request('http://press-zone.test/api/webhooks/paypal', {
      method: 'POST',
      headers: createWebhookHeaders(),
      body: settlementBody('WH-SETTLEMENT-1'),
    })
    const second = await app.request('http://press-zone.test/api/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 countRows(
        db,
        'wallet_balances',
        `WHERE owner_id = '${walletOwnerId.replaceAll("'", "''")}'`,
      ),
    ).toBe(1)
    expect(await countRows(db, 'invoice_references')).toBe(1)
    expect(await countRows(db, 'charge_intents')).toBe(1)
    expect(await countRows(db, 'ledger_entries')).toBe(1)

    const [chargeIntent] = await db
      .select({
        status: chargeIntents.status,
        settledAt: chargeIntents.settledAt,
      })
      .from(chargeIntents)
      .where(eq(chargeIntents.chargeKey, `${PAYPAL_SUBSCRIPTION_ID}:${PERIOD}`))
      .limit(1)

    expect(chargeIntent).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:${PAYPAL_SUBSCRIPTION_ID}:${PERIOD}`,
    )
  })
})
