import { and, eq, isNull, or } from 'drizzle-orm'
import type { Schema } from '@platform-modules/db'
import { EntitlementFeatureError, EntitlementTierError } from './errors.js'
import { entitlementTierGrants, entitlements } from './schema.js'
import type {
  EntitlementDatabase,
  EntitlementQuerier,
  EntitlementSubject,
  EntitlementsService,
  TierRef,
} from './types.js'

function resolveNow(db: unknown): Date {
  if (
    typeof db === 'object' &&
    db !== null &&
    '__entitlementsNow' in db &&
    typeof (db as { __entitlementsNow?: unknown }).__entitlementsNow === 'function'
  ) {
    return (db as { __entitlementsNow: () => Date }).__entitlementsNow()
  }

  return new Date()
}

function isLive(expiresAt: Date | null, now: Date): boolean {
  return expiresAt === null || expiresAt >= now
}

function accountFromSubject(subject: EntitlementSubject): string {
  if (typeof subject === 'string' && subject.trim() !== '') {
    return subject
  }
  if (typeof subject === 'object' && subject !== null && typeof subject.account === 'string') {
    const account = subject.account.trim()
    if (account !== '') {
      return account
    }
  }
  throw new EntitlementFeatureError('subject', 'account')
}

async function activeGrant<S extends Schema>(
  db: EntitlementQuerier<S>,
  account: string,
  capability: string,
  now: Date,
) {
  const [row] = await db
    .select()
    .from(entitlements)
    .where(and(eq(entitlements.account, account), eq(entitlements.capability, capability)))
    .limit(1)

  if (!row || !isLive(row.expiresAt, now)) {
    return undefined
  }

  return row
}

async function requireTierGrant<S extends Schema>(
  db: EntitlementQuerier<S>,
  input: TierRef,
): Promise<void> {
  const [grant] = await db
    .select({ capability: entitlementTierGrants.capability })
    .from(entitlementTierGrants)
    .where(
      and(
        eq(entitlementTierGrants.capability, input.capability),
        eq(entitlementTierGrants.tier, input.tier),
      ),
    )
    .limit(1)

  if (!grant) {
    throw new EntitlementTierError(input.tier)
  }
}

export function createEntitlements<S extends Schema>(input: {
  db: EntitlementDatabase<S>
}): EntitlementsService {
  const { db } = input

  return {
    async can(subject, capability) {
      const row = await activeGrant(db, accountFromSubject(subject), capability, resolveNow(db))
      return row !== undefined
    },

    async quota(subject, key) {
      const account = accountFromSubject(subject)
      const row = await activeGrant(db, account, `plugin:${key.split(':', 1)[0]}`, resolveNow(db))
      if (!row) {
        return 0
      }

      const [grant] = await db
        .select({ quotaValue: entitlementTierGrants.quotaValue })
        .from(entitlementTierGrants)
        .where(
          and(
            eq(entitlementTierGrants.capability, row.capability),
            eq(entitlementTierGrants.tier, row.tier),
            eq(entitlementTierGrants.quotaKey, key),
          ),
        )
        .limit(1)

      if (!grant || grant.quotaValue === null) {
        return 0
      }

      return grant.quotaValue
    },

    async setTier(subject, tierRef) {
      const account = accountFromSubject(subject)
      await requireTierGrant(db, tierRef)

      await db.transaction(async (tx) => {
        await tx
          .insert(entitlements)
          .values({
            account,
            capability: tierRef.capability,
            tier: tierRef.tier,
            grantedAt: resolveNow(db),
            expiresAt: null,
          })
          .onConflictDoUpdate({
            target: [entitlements.account, entitlements.capability],
            set: {
              tier: tierRef.tier,
              expiresAt: null,
            },
          })
      })
    },
  }
}

export {
  EntitlementFeatureError,
  EntitlementTierError,
  isEntitlementError,
  isEntitlementFeatureError,
  isEntitlementTierError,
} from './errors.js'
export { entitlementTierGrants, entitlements, entitlementsSchema } from './schema.js'
export type { EntitlementError } from './errors.js'
export type {
  EntitlementClock,
  EntitlementDatabase,
  EntitlementGrant,
  EntitlementQuerier,
  EntitlementSubject,
  EntitlementTierGrant,
  EntitlementsSchema,
  EntitlementsService,
  TierRef,
} from './types.js'
