import { Hono } from 'hono'
import { and, desc, eq, like } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { NotAMemberError, type Tenancy } from '@platform-modules/tenancy'

import { AppError } from '../errors.js'
import { ok } from '../http.js'
import {
  authUsers,
  invoiceReferences,
  members,
  sites,
  subscriptionPackages,
  subscriptions,
  type PressZoneSchema,
  walletBalances,
} from '../schema.js'
import type { AuthPrincipal } from '../mw/auth.js'

type AccountRouteEnv = {
  Variables: {
    capabilities: string[]
    principal: AuthPrincipal
  }
}

type PressZoneDb = Querier<PressZoneSchema>

export const ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY = 'account.members.write'

export interface AccountRouteDeps {
  db: PressZoneDb
  tenancy: Pick<Tenancy, 'setMemberRole'>
}

type RolePatchBody = {
  roleKey?: unknown
}

function assertAccountScope(principalAccount: string, requestedAccount: string): void {
  if (principalAccount !== requestedAccount) {
    throw new AppError('FORBIDDEN', 'Forbidden', 403)
  }
}

function assertCapability(capabilities: string[], capability: string): void {
  if (!capabilities.includes(capability)) {
    throw new AppError('FORBIDDEN', 'Forbidden', 403)
  }
}

function parseRoleKey(body: RolePatchBody): string {
  if (typeof body.roleKey !== 'string' || body.roleKey.trim().length === 0) {
    throw new AppError('INVALID_REQUEST', 'roleKey is required', 400)
  }

  return body.roleKey.trim()
}

async function readDashboard(db: PressZoneDb, accountId: string) {
  const [subscriptionRows, siteRows, memberRows, invoiceRows, walletRows] = await Promise.all([
    db
      .select({
        id: subscriptions.id,
        pluginKey: subscriptions.pluginKey,
        tierKey: subscriptions.tierKey,
        status: subscriptions.status,
        currentPeriodStart: subscriptions.currentPeriodStart,
        currentPeriodEnd: subscriptions.currentPeriodEnd,
        gracePeriodEndAt: subscriptions.gracePeriodEndAt,
        paypalSubscriptionId: subscriptions.paypalSubscriptionId,
        createdAt: subscriptions.createdAt,
        currency: subscriptionPackages.currency,
        priceMinor: subscriptionPackages.priceMinor,
        creditAllocation: subscriptionPackages.creditAllocation,
        seatsConfig: subscriptionPackages.seatsConfig,
      })
      .from(subscriptions)
      .leftJoin(
        subscriptionPackages,
        and(
          eq(subscriptionPackages.pluginKey, subscriptions.pluginKey),
          eq(subscriptionPackages.tierKey, subscriptions.tierKey),
        ),
      )
      .where(eq(subscriptions.accountId, accountId))
      .orderBy(subscriptions.createdAt),
    db
      .select({
        id: sites.id,
        pluginKey: sites.pluginKey,
        displayUrl: sites.displayUrl,
        status: sites.status,
        createdAt: sites.createdAt,
        disconnectedAt: sites.disconnectedAt,
      })
      .from(sites)
      .where(eq(sites.accountId, accountId))
      .orderBy(sites.createdAt),
    db
      .select({
        userId: members.userId,
        roleKey: members.roleKey,
        status: members.status,
        createdAt: members.createdAt,
        email: authUsers.email,
      })
      .from(members)
      .leftJoin(authUsers, eq(authUsers.id, members.userId))
      .where(eq(members.accountId, accountId))
      .orderBy(members.createdAt),
    db
      .select({
        id: invoiceReferences.id,
        subscriptionId: invoiceReferences.subscriptionId,
        documentNumber: invoiceReferences.documentNumber,
        documentUrl: invoiceReferences.documentUrl,
        docType: invoiceReferences.docType,
        amount: invoiceReferences.amount,
        vatAmount: invoiceReferences.vatAmount,
        currency: invoiceReferences.currency,
        createdAt: invoiceReferences.createdAt,
      })
      .from(invoiceReferences)
      .where(eq(invoiceReferences.accountId, accountId))
      .orderBy(desc(invoiceReferences.createdAt)),
    db
      .select({
        ownerId: walletBalances.ownerId,
        balance: walletBalances.balance,
      })
      .from(walletBalances)
      .where(like(walletBalances.ownerId, `${accountId}:%`))
      .orderBy(walletBalances.ownerId),
  ])

  let totalBalanceMinor = 0n
  for (const wallet of walletRows) {
    totalBalanceMinor += wallet.balance
  }

  return {
    accountId,
    subscriptions: subscriptionRows.map((row) => ({
      id: row.id,
      pluginKey: row.pluginKey,
      tierKey: row.tierKey,
      status: row.status,
      currentPeriodStart: row.currentPeriodStart,
      currentPeriodEnd: row.currentPeriodEnd,
      gracePeriodEndAt: row.gracePeriodEndAt,
      paypalSubscriptionId: row.paypalSubscriptionId,
      createdAt: row.createdAt,
      package: row.currency
        ? {
            currency: row.currency,
            priceMinor: row.priceMinor,
            creditAllocation: row.creditAllocation,
            seats: row.seatsConfig,
          }
        : null,
    })),
    sites: siteRows,
    seats: {
      used: memberRows.filter((row) => row.status === 'active').length,
      byPlugin: subscriptionRows
        .filter((row) => row.seatsConfig !== null)
        .map((row) => ({
          pluginKey: row.pluginKey,
          limit: row.seatsConfig as number,
        })),
    },
    members: memberRows,
    roles: [...new Set(memberRows.map((row) => row.roleKey))].sort(),
    invoices: invoiceRows,
    creditBalance: {
      totalMinor: totalBalanceMinor,
      wallets: walletRows,
    },
  }
}

export function createAccountRoute(deps: AccountRouteDeps) {
  const route = new Hono<AccountRouteEnv>()

  route.get('/', async (context) => {
    const principal = context.get('principal')
    return ok(await readDashboard(deps.db, principal.account))
  })

  route.get('/:accountId', async (context) => {
    const principal = context.get('principal')
    const accountId = context.req.param('accountId')

    assertAccountScope(principal.account, accountId)
    return ok(await readDashboard(deps.db, accountId))
  })

  route.patch('/:accountId/members/:userId', async (context) => {
    const principal = context.get('principal')
    const capabilities = context.get('capabilities')
    const accountId = context.req.param('accountId')
    const userId = context.req.param('userId')

    assertAccountScope(principal.account, accountId)
    assertCapability(capabilities, ACCOUNT_MEMBER_ROLE_WRITE_CAPABILITY)

    const roleKey = parseRoleKey((await context.req.json()) as RolePatchBody)

    try {
      const member = await deps.tenancy.setMemberRole(accountId, userId, roleKey)
      return ok({ member })
    } catch (error) {
      if (error instanceof NotAMemberError) {
        throw new AppError('MEMBER_NOT_FOUND', 'Member not found', 404)
      }

      throw error
    }
  })

  return route
}
