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

import {
  entitlementTierGrants,
  entitlements,
  pressZoneInitSql,
  plugins,
  pressZoneSchema,
  subscriptionPackages,
  subscriptions,
  walletBalances,
} from '../schema.js'
import { periodKey } from '../lib/wallet.js'
import { subscribeAccount } from './subscribe.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 seedCatalog(db: TestDb): Promise<void> {
  await db.insert(plugins).values({
    key: 'translate',
    name: 'Translate',
    permissionCatalog: ['plugin:translate'],
  })

  await db.insert(subscriptionPackages).values({
    pluginKey: 'translate',
    tierKey: 'pro',
    currency: 'USD',
    priceMinor: 1_900n,
    creditAllocation: 100n,
    seatsConfig: 5,
    paypalPlanId: 'plan_translate_pro',
  })
  await db.insert(entitlementTierGrants).values([
    {
      capability: 'plugin:translate',
      tier: 'pro',
      quotaKey: '',
      quotaValue: null,
    },
    {
      capability: 'plugin:translate',
      tier: 'pro',
      quotaKey: 'translate:chars',
      quotaValue: 100,
    },
  ])
}

async function seedAccount(db: TestDb, accountId: string): Promise<void> {
  await db.execute(
    sql`INSERT INTO accounts (id, slug, type, status) VALUES (${accountId}::uuid, ${`acct-${accountId}`}, 'individual', 'active')`,
  )
}

async function countSubscriptions(db: TestDb): Promise<number> {
  const rows = await db.select().from(subscriptions)
  return rows.length
}

async function countEntitlements(db: TestDb, accountId: string): Promise<number> {
  const rows = await db.select().from(entitlements).where(eq(entitlements.account, accountId))
  return rows.length
}

async function readWalletBalance(db: TestDb, 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
}

describe('subscribeAccount', () => {
  it('creates the subscription, sets the entitlement tier, and seeds the first-period wallet', async () => {
    const db = await createTestDb()
    await seedCatalog(db)
    await seedAccount(db, '00000000-0000-0000-0000-000000000111')
    const createSubscription = vi.fn(async () => ({
      id: 'I-SUB-1',
      status: 'ACTIVE',
      currentPeriod: '2026-07',
      planId: 'plan_translate_pro',
    }))

    const result = await subscribeAccount({
      accountId: '00000000-0000-0000-0000-000000000111',
      db,
      now: () => new Date('2026-07-04T00:00:00.000Z'),
      provider: { createSubscription },
      pluginKey: 'translate',
      tierKey: 'pro',
    })

    expect(result).toEqual({
      accountId: '00000000-0000-0000-0000-000000000111',
      currentPeriod: '2026-07',
      paypalSubscriptionId: 'I-SUB-1',
      pluginKey: 'translate',
      status: 'ACTIVE',
      tierKey: 'pro',
      walletKey: '00000000-0000-0000-0000-000000000111:translate:2026-07',
    })
    expect(createSubscription).toHaveBeenCalledWith({
      currentPeriod: '2026-07',
      idempotencyKey: 'subscribe:00000000-0000-0000-0000-000000000111:translate:pro',
      planId: 'plan_translate_pro',
    })
    expect(await countSubscriptions(db)).toBe(1)
    expect(await countEntitlements(db, '00000000-0000-0000-0000-000000000111')).toBeGreaterThan(0)
    expect(
      await readWalletBalance(
        db,
        periodKey('00000000-0000-0000-0000-000000000111', 'translate', '2026-07'),
      ),
    ).toBe(100n)
  })

  it('is idempotent for a repeated subscribe on the same account and package', async () => {
    const db = await createTestDb()
    await seedCatalog(db)
    await seedAccount(db, '00000000-0000-0000-0000-000000000222')
    const createSubscription = vi.fn(async () => ({
      id: 'I-SUB-2',
      status: 'ACTIVE',
      currentPeriod: '2026-07',
      planId: 'plan_translate_pro',
    }))

    const input = {
      accountId: '00000000-0000-0000-0000-000000000222',
      db,
      now: () => new Date('2026-07-04T00:00:00.000Z'),
      provider: { createSubscription },
      pluginKey: 'translate',
      tierKey: 'pro',
    } as const

    const first = await subscribeAccount(input)
    const second = await subscribeAccount(input)

    expect(second).toEqual(first)
    expect(createSubscription).toHaveBeenCalledTimes(1)
    expect(await countSubscriptions(db)).toBe(1)
    expect(await countEntitlements(db, '00000000-0000-0000-0000-000000000222')).toBeGreaterThan(0)
    expect(
      await readWalletBalance(
        db,
        periodKey('00000000-0000-0000-0000-000000000222', 'translate', '2026-07'),
      ),
    ).toBe(100n)
  })

  it('leaves no subscription, entitlement, or wallet when the provider errors', async () => {
    const db = await createTestDb()
    await seedCatalog(db)
    await seedAccount(db, '00000000-0000-0000-0000-000000000333')
    const createSubscription = vi.fn(async () => {
      throw new Error('provider unavailable')
    })

    await expect(
      subscribeAccount({
        accountId: '00000000-0000-0000-0000-000000000333',
        db,
        now: () => new Date('2026-07-04T00:00:00.000Z'),
        provider: { createSubscription },
        pluginKey: 'translate',
        tierKey: 'pro',
      }),
    ).rejects.toThrow('provider unavailable')

    expect(await countSubscriptions(db)).toBe(0)
    expect(await countEntitlements(db, '00000000-0000-0000-0000-000000000333')).toBe(0)
    expect(
      await readWalletBalance(
        db,
        periodKey('00000000-0000-0000-0000-000000000333', 'translate', '2026-07'),
      ),
    ).toBe(0n)
  })
})
