import { describe, expect, it } from 'vitest'
import { eq, sql } from 'drizzle-orm'
import { createPgliteClient } from '@platform-modules/db/pglite'
import type { MiddlewareHandler } from 'hono'

import {
  createApp,
  releaseRefundReservation,
  reserveRefund,
} from './index.js'
import { createOauthRoute } from './routes/oauth.js'
import { createWebhookRoute } from './routes/webhooks.js'
import { createSubscribeRoute } from './routes/subscribe.js'
import { createPluginRoute } from './routes/plugin.js'
import { createAccountRoute } from './routes/account.js'
import { createStaffRoute } from './routes/staff.js'
import { fail } from './http.js'
import {
  accounts,
  chargeIntents,
  plugins,
  pressZoneInitSql,
  pressZoneSchema,
  subscriptionPackages,
  subscriptions,
} from './schema.js'

type TestDb = ReturnType<typeof createPgliteClient<typeof pressZoneSchema>>

async function createTestDb(): Promise<TestDb> {
  const db = createPgliteClient({ schema: pressZoneSchema })

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

  return db
}

async function seedRefundableCharge(db: TestDb, input: { amount: bigint; currency: string }) {
  const [account] = await db.insert(accounts).values({ slug: 'acct-1' }).returning({ id: accounts.id })
  if (!account) {
    throw new Error('account not created')
  }

  await db.insert(plugins).values({ key: 'translate', name: 'Translate' })
  await db.insert(subscriptionPackages).values({
    pluginKey: 'translate',
    tierKey: 'pro',
    currency: input.currency,
    priceMinor: 1000n,
    creditAllocation: 100n,
    seatsConfig: 1,
    paypalPlanId: 'P-TRANSLATE',
  })

  const paypalSubscriptionId = 'I-SUB-1'
  await db.insert(subscriptions).values({
    accountId: account.id,
    pluginKey: 'translate',
    tierKey: 'pro',
    status: 'active',
    paypalSubscriptionId,
  })
  await db.insert(chargeIntents).values({
    chargeKey: `${paypalSubscriptionId}:2026-07`,
    amount: input.amount,
    currency: input.currency,
    status: 'settled',
  })

  return {
    accountId: account.id,
    chargeKey: `${paypalSubscriptionId}:2026-07`,
    walletKey: `${account.id}:translate:2026-07`,
  }
}

function createMountedApp(db: TestDb) {
  const unauthorized: MiddlewareHandler = async () =>
    fail('UNAUTHORIZED', 'Authentication required', 401)

  return createApp({
    accountRoute: createAccountRoute({
      db,
      tenancy: {
        setMemberRole: async () => {
          throw new Error('not implemented')
        },
      },
    }),
    oauthRoute: createOauthRoute({ db }),
    pluginRoute: createPluginRoute({
      auth: unauthorized,
      access: unauthorized,
      cost: 1n,
      currentPeriod: () => '2026-07',
      execute: async () => ({ ok: true }),
      getDb: () => db,
      plugin: 'translate',
    }),
    staffRoute: createStaffRoute({
      adjustCredits: async () => ({ balance: 0n }),
      audit: async () => {},
      reserveRefund: async () => ({ status: 'reserved' }),
      releaseRefundReservation: async () => undefined,
      recordRefundCredit: async () => ({
        balance: 0n,
        creditNote: {
          documentId: 'credit-note',
          documentNumber: 'CN-1',
          documentUrl: 'https://example.test/credit-note',
        },
      }),
      listAudit: async () => ({ items: [], nextCursor: null }),
      refundProvider: { refund: async () => ({ kind: 'pending' }) },
      setTier: async () => {},
    }),
    subscribeRoute: createSubscribeRoute({
      db,
      provider: {
        createSubscription: async () => ({
          id: 'I-SUB',
          status: 'ACTIVE',
          currentPeriod: '2026-07',
          planId: 'plan-test',
        }),
      },
    }),
    webhookRoute: createWebhookRoute({
      db: Object.assign(db, {
        morningCredential: {
          apiUser: 'api-user@example.test',
          apiPass: 'secret',
          companyId: 'company-123',
        },
        morningProvider: {
          issue: async () => ({
            documentId: 'invoice-1',
            documentNumber: 'INV-1',
            documentUrl: 'https://example.test/invoice',
          }),
        },
        invoiceDate: '2026-07-05',
        invoiceCurrency: 'ILS',
      }),
      paypalProvider: {
        provider: 'paypal',
        emitsInvoiceOnCharge: false,
        charge: async () => {
          throw new Error('not implemented')
        },
        refund: async () => ({ kind: 'pending' }),
        parseWebhook: async () => ({ eventId: 'evt-webhook', kind: 'other', raw: {} }),
      },
    }),
    protectedMiddleware: unauthorized,
  })
}

async function readJson(response: Response) {
  return response.json()
}

describe('createApp integration mounts', () => {
  it('mounts public and authenticated groups with the required auth boundaries', async () => {
    const db = await createTestDb()
    const app = createMountedApp(db)

    const healthResponse = await app.request('http://press-zone.test/health')
    expect(healthResponse.status).toBe(200)
    await expect(readJson(healthResponse)).resolves.toEqual({ data: { ok: true } })

    const guardedResponse = await app.request('http://press-zone.test/api/plugin/translate', {
      method: 'POST',
    })
    expect(guardedResponse.status).toBe(401)
    await expect(readJson(guardedResponse)).resolves.toEqual({
      error: {
        code: 'UNAUTHORIZED',
        message: 'Authentication required',
      },
    })

    const oauthTokenResponse = await app.request('http://press-zone.test/oauth/token', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({}),
    })
    expect(oauthTokenResponse.status).toBe(400)
    await expect(readJson(oauthTokenResponse)).resolves.toEqual({
      error: {
        code: 'OAUTH_INVALID',
        message: 'Invalid oauth token request',
      },
    })

    const webhookResponse = await app.request('http://press-zone.test/api/webhooks/paypal', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ id: 'evt-webhook' }),
    })
    expect(webhookResponse.status).toBe(200)

    const notFoundResponse = await app.request('http://press-zone.test/not-mounted')
    expect(notFoundResponse.status).toBe(404)
    await expect(readJson(notFoundResponse)).resolves.toEqual({
      error: {
        code: 'NOT_FOUND',
        message: 'Route not found',
      },
    })
  })
})

describe('reserveRefund', () => {
  it('atomically reserves against the charge cap so concurrent distinct refund ids cannot over-reserve', async () => {
    const db = await createTestDb()
    const seeded = await seedRefundableCharge(db, { amount: 100n, currency: 'ILS' })

    const [first, second] = await Promise.allSettled([
      reserveRefund(db, {
        chargeKey: seeded.chargeKey,
        refundKey: 'staff-refund:one',
        walletKey: seeded.walletKey,
        amountMinor: 60n,
        currency: 'ILS',
      }),
      reserveRefund(db, {
        chargeKey: seeded.chargeKey,
        refundKey: 'staff-refund:two',
        walletKey: seeded.walletKey,
        amountMinor: 50n,
        currency: 'ILS',
      }),
    ])

    const fulfilled = [first, second].filter((result) => result.status === 'fulfilled')
    const rejected = [first, second].filter((result) => result.status === 'rejected')

    expect(fulfilled).toHaveLength(1)
    expect(rejected).toHaveLength(1)
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({
      message: 'refund exceeds captured charge amount',
    })

    const [charge] = await db
      .select({ refundReservedMinor: chargeIntents.refundReservedMinor })
      .from(chargeIntents)
      .where(eq(chargeIntents.chargeKey, seeded.chargeKey))
      .limit(1)

    expect(charge?.refundReservedMinor).toBe(60n)
  })

  it('releases a failed reservation so the cap can be retried', async () => {
    const db = await createTestDb()
    const seeded = await seedRefundableCharge(db, { amount: 100n, currency: 'ILS' })

    await reserveRefund(db, {
      chargeKey: seeded.chargeKey,
      refundKey: 'staff-refund:retry',
      walletKey: seeded.walletKey,
      amountMinor: 80n,
      currency: 'ILS',
    })

    await releaseRefundReservation(db, { refundKey: 'staff-refund:retry' })

    await expect(
      reserveRefund(db, {
        chargeKey: seeded.chargeKey,
        refundKey: 'staff-refund:retry-2',
        walletKey: seeded.walletKey,
        amountMinor: 100n,
        currency: 'ILS',
      }),
    ).resolves.toEqual({ status: 'reserved' })
  })
})
