import { Hono } from 'hono'
import { and, eq, sql } from 'drizzle-orm'
import { beforeEach, describe, expect, it } from 'vitest'
import type { Membership, Tenancy } from '@platform-modules/tenancy'
import { createPgliteClient } from '@platform-modules/db/pglite'

import { onError } from '../http.js'
import {
  accounts,
  authUsers,
  invoiceReferences,
  members,
  plugins,
  pressZoneInitSql,
  pressZoneSchema,
  sites,
  subscriptionPackages,
  subscriptions,
  walletBalances,
} from '../schema.js'
import {
  ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY,
  createAccountRoute,
} from './account.js'

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

const ACCOUNT_A = '00000000-0000-0000-0000-0000000000a1'
const ACCOUNT_B = '00000000-0000-0000-0000-0000000000b2'
const SUBSCRIPTION_A = '10000000-0000-0000-0000-0000000000a1'
const SUBSCRIPTION_B = '10000000-0000-0000-0000-0000000000b2'
const SITE_A = '20000000-0000-0000-0000-0000000000a1'
const SITE_B = '20000000-0000-0000-0000-0000000000b2'

async function readJson(response: Response): Promise<unknown> {
  return response.json()
}

async function createDb(): Promise<PressZoneDb> {
  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 seedDashboardFixture(db: PressZoneDb): Promise<void> {
  const now = new Date('2026-07-04T10:00:00.000Z')

  await db.insert(accounts).values([
    { id: ACCOUNT_A, slug: 'account-a', type: 'org', status: 'active', createdAt: now },
    { id: ACCOUNT_B, slug: 'account-b', type: 'org', status: 'active', createdAt: now },
  ])

  await db.insert(authUsers).values([
    {
      id: 'user-a-owner',
      email: 'owner-a@example.com',
      passwordHash: null,
      sessionVersion: 0,
      roles: '["user"]',
      status: 'active',
      createdAt: now,
    },
    {
      id: 'user-a-member',
      email: 'member-a@example.com',
      passwordHash: null,
      sessionVersion: 0,
      roles: '["user"]',
      status: 'active',
      createdAt: now,
    },
    {
      id: 'user-b-owner',
      email: 'owner-b@example.com',
      passwordHash: null,
      sessionVersion: 0,
      roles: '["user"]',
      status: 'active',
      createdAt: now,
    },
  ])

  await db.insert(plugins).values([{ key: 'translate', name: 'Translate', permissionCatalog: [] }])

  await db.insert(subscriptionPackages).values({
    pluginKey: 'translate',
    tierKey: 'pro',
    currency: 'USD',
    priceMinor: 4900n,
    creditAllocation: 1000n,
    seatsConfig: 5,
    paypalPlanId: 'plan_translate_pro',
    createdAt: now,
  })

  await db.insert(subscriptions).values([
    {
      id: SUBSCRIPTION_A,
      accountId: ACCOUNT_A,
      pluginKey: 'translate',
      tierKey: 'pro',
      status: 'active',
      currentPeriodStart: new Date('2026-07-01T00:00:00.000Z'),
      currentPeriodEnd: new Date('2026-08-01T00:00:00.000Z'),
      paypalSubscriptionId: 'paypal-sub-a',
      createdAt: now,
      updatedAt: now,
    },
    {
      id: SUBSCRIPTION_B,
      accountId: ACCOUNT_B,
      pluginKey: 'translate',
      tierKey: 'pro',
      status: 'active',
      currentPeriodStart: new Date('2026-07-01T00:00:00.000Z'),
      currentPeriodEnd: new Date('2026-08-01T00:00:00.000Z'),
      paypalSubscriptionId: 'paypal-sub-b',
      createdAt: now,
      updatedAt: now,
    },
  ])

  await db.insert(sites).values([
    {
      id: SITE_A,
      accountId: ACCOUNT_A,
      pluginKey: 'translate',
      displayUrl: 'https://a.example.com',
      status: 'active',
      createdAt: now,
    },
    {
      id: SITE_B,
      accountId: ACCOUNT_B,
      pluginKey: 'translate',
      displayUrl: 'https://b.example.com',
      status: 'active',
      createdAt: now,
    },
  ])

  await db.insert(members).values([
    {
      accountId: ACCOUNT_A,
      userId: 'user-a-owner',
      roleKey: 'OWNER',
      status: 'active',
      createdAt: now,
    },
    {
      accountId: ACCOUNT_A,
      userId: 'user-a-member',
      roleKey: 'EDITOR',
      status: 'active',
      createdAt: now,
    },
    {
      accountId: ACCOUNT_B,
      userId: 'user-b-owner',
      roleKey: 'OWNER',
      status: 'active',
      createdAt: now,
    },
  ])

  await db.insert(invoiceReferences).values([
    {
      id: '30000000-0000-0000-0000-0000000000a1',
      accountId: ACCOUNT_A,
      subscriptionId: SUBSCRIPTION_A,
      morningDocumentId: 'morning-a',
      documentNumber: 'INV-A',
      documentUrl: 'https://invoices.example.com/a.pdf',
      docType: 'invoice',
      amount: 4900n,
      vatAmount: 0n,
      currency: 'USD',
      idempotencyKey: 'inv-a',
      createdAt: now,
    },
    {
      id: '30000000-0000-0000-0000-0000000000b2',
      accountId: ACCOUNT_B,
      subscriptionId: SUBSCRIPTION_B,
      morningDocumentId: 'morning-b',
      documentNumber: 'INV-B',
      documentUrl: 'https://invoices.example.com/b.pdf',
      docType: 'invoice',
      amount: 4900n,
      vatAmount: 0n,
      currency: 'USD',
      idempotencyKey: 'inv-b',
      createdAt: now,
    },
  ])

  await db.insert(walletBalances).values([
    { ownerId: `${ACCOUNT_A}:translate:2026-07`, balance: 275n, updatedAt: now },
    { ownerId: `${ACCOUNT_B}:translate:2026-07`, balance: 900n, updatedAt: now },
  ])
}

function createTenancy(db: PressZoneDb): Pick<Tenancy, 'setMemberRole'> {
  return {
    async setMemberRole(tenantId, userId, roleKey) {
      const [updated] = await db
        .update(members)
        .set({ roleKey })
        .where(and(eq(members.accountId, tenantId), eq(members.userId, userId)))
        .returning({
          tenantId: members.accountId,
          userId: members.userId,
          roleKey: members.roleKey,
          status: members.status,
        })

      if (!updated) {
        throw new Error(`member not found: ${tenantId}/${userId}`)
      }

      return {
        tenantId: updated.tenantId,
        userId: updated.userId,
        roleKey: updated.roleKey,
        status: updated.status as Membership['status'],
      }
    },
  }
}

function createApp(
  db: PressZoneDb,
  input?: { account?: string; capabilities?: string[]; tenancy?: Pick<Tenancy, 'setMemberRole'> },
) {
  const app = new Hono()

  app.onError(onError)
  app.use('/account/*', async (context, next) => {
    context.set('principal', {
      account: input?.account ?? ACCOUNT_A,
      userId: 'user-a-owner',
      scopes: input?.capabilities ?? [ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY],
    })
    context.set('capabilities', input?.capabilities ?? [ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY])
    await next()
  })
  app.route(
    '/account',
    createAccountRoute({
      db,
      tenancy: input?.tenancy ?? createTenancy(db),
    }),
  )

  return app
}

describe('account route', () => {
  let db: PressZoneDb

  beforeEach(async () => {
    db = await createDb()
    await seedDashboardFixture(db)
  })

  it('lists only the authenticated account dashboard data', async () => {
    const app = createApp(db)

    const response = await app.request('http://press-zone.test/account')

    expect(response.status).toBe(200)
    await expect(readJson(response)).resolves.toEqual({
      data: {
        accountId: ACCOUNT_A,
        subscriptions: [
          {
            id: SUBSCRIPTION_A,
            pluginKey: 'translate',
            tierKey: 'pro',
            status: 'active',
            currentPeriodStart: '2026-07-01T00:00:00.000Z',
            currentPeriodEnd: '2026-08-01T00:00:00.000Z',
            gracePeriodEndAt: null,
            paypalSubscriptionId: 'paypal-sub-a',
            createdAt: '2026-07-04T10:00:00.000Z',
            package: {
              currency: 'USD',
              priceMinor: '4900',
              creditAllocation: '1000',
              seats: 5,
            },
          },
        ],
        sites: [
          {
            id: SITE_A,
            pluginKey: 'translate',
            displayUrl: 'https://a.example.com',
            status: 'active',
            createdAt: '2026-07-04T10:00:00.000Z',
            disconnectedAt: null,
          },
        ],
        seats: {
          used: 2,
          byPlugin: [{ pluginKey: 'translate', limit: 5 }],
        },
        members: [
          {
            userId: 'user-a-owner',
            roleKey: 'OWNER',
            status: 'active',
            createdAt: '2026-07-04T10:00:00.000Z',
            email: 'owner-a@example.com',
          },
          {
            userId: 'user-a-member',
            roleKey: 'EDITOR',
            status: 'active',
            createdAt: '2026-07-04T10:00:00.000Z',
            email: 'member-a@example.com',
          },
        ],
        roles: ['EDITOR', 'OWNER'],
        invoices: [
          {
            id: '30000000-0000-0000-0000-0000000000a1',
            subscriptionId: SUBSCRIPTION_A,
            documentNumber: 'INV-A',
            documentUrl: 'https://invoices.example.com/a.pdf',
            docType: 'invoice',
            amount: '4900',
            vatAmount: '0',
            currency: 'USD',
            createdAt: '2026-07-04T10:00:00.000Z',
          },
        ],
        creditBalance: {
          totalMinor: '275',
          wallets: [{ ownerId: `${ACCOUNT_A}:translate:2026-07`, balance: '275' }],
        },
      },
    })
  })

  it('returns 403 when a caller requests another account dashboard', async () => {
    const app = createApp(db)

    const response = await app.request(`http://press-zone.test/account/${ACCOUNT_B}`)

    expect(response.status).toBe(403)
    await expect(readJson(response)).resolves.toEqual({
      error: {
        code: 'FORBIDDEN',
        message: 'Forbidden',
      },
    })
  })

  it('updates a member role through the tenancy path', async () => {
    const app = createApp(db, {
      capabilities: [ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY],
    })

    const response = await app.request(
      `http://press-zone.test/account/${ACCOUNT_A}/members/user-a-member`,
      {
        method: 'PATCH',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ roleKey: 'BILLING_ADMIN' }),
      },
    )

    expect(response.status).toBe(200)
    await expect(readJson(response)).resolves.toEqual({
      data: {
        member: {
          tenantId: ACCOUNT_A,
          userId: 'user-a-member',
          roleKey: 'BILLING_ADMIN',
          status: 'active',
        },
      },
    })

    const [updated] = await db
      .select({ roleKey: members.roleKey })
      .from(members)
      .where(
        and(eq(members.accountId, ACCOUNT_A), eq(members.userId, 'user-a-member')),
      )
      .limit(1)

    expect(updated?.roleKey).toBe('BILLING_ADMIN')
  })
})
