import { and, eq, gt, isNull, or } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import type { Principal } from '../index.js'
import { generateOpaqueToken, hashToken } from '@platform-modules/util/tokens'
import { authUsers, serviceTokens } from './schema.js'

const LAST_USED_THROTTLE_MS = 60 * 60 * 1000 // ~hourly — avoid a write on every agent request

function parseRoles(raw: string): string[] {
  try {
    const parsed = JSON.parse(raw) as unknown
    return Array.isArray(parsed) ? (parsed as string[]) : ['user']
  } catch {
    return ['user']
  }
}

export async function issueServiceToken<S extends Record<string, unknown>>(
  db: Querier<S>,
  tables: { authUsers: typeof authUsers; serviceTokens: typeof serviceTokens },
  input: { userId: string; label: string; ttlMs?: number; now?: Date },
): Promise<{ id: string; token: string }> {
  const now = input.now ?? new Date()
  const id = crypto.randomUUID()
  const token = generateOpaqueToken()
  const tokenHash = await hashToken(token)
  const expiresAt = input.ttlMs != null ? new Date(now.getTime() + input.ttlMs) : null
  await db.insert(tables.serviceTokens).values({
    id,
    userId: input.userId,
    tokenHash,
    label: input.label,
    expiresAt,
    status: 'active',
    createdAt: now,
    lastUsedAt: null,
  })
  return { id, token }
}

export async function resolveServiceToken<S extends Record<string, unknown>>(
  db: Querier<S>,
  tables: { authUsers: typeof authUsers; serviceTokens: typeof serviceTokens },
  rawToken: string,
  opts?: { now?: Date },
): Promise<Principal | null> {
  const now = opts?.now ?? new Date()
  const tokenHash = await hashToken(rawToken)

  const [row] = await db
    .select({
      id: tables.serviceTokens.id,
      userId: tables.serviceTokens.userId,
      lastUsedAt: tables.serviceTokens.lastUsedAt,
      roles: tables.authUsers.roles,
    })
    .from(tables.serviceTokens)
    .innerJoin(tables.authUsers, eq(tables.serviceTokens.userId, tables.authUsers.id))
    .where(
      and(
        eq(tables.serviceTokens.tokenHash, tokenHash),
        eq(tables.serviceTokens.status, 'active'),
        // Fail-closed on the OWNER's status — a disabled user's PAT resolves to null even
        // before revoke-on-disable lands (defense-in-depth, matches verifySession live read).
        eq(tables.authUsers.status, 'active'),
        or(isNull(tables.serviceTokens.expiresAt), gt(tables.serviceTokens.expiresAt, now)),
      ),
    )
    .limit(1)

  if (!row) return null

  // best-effort throttled last-used; MUST NOT change the verdict if it fails.
  const last = row.lastUsedAt ? new Date(row.lastUsedAt).getTime() : 0
  if (now.getTime() - last >= LAST_USED_THROTTLE_MS) {
    try {
      await db
        .update(tables.serviceTokens)
        .set({ lastUsedAt: now })
        .where(eq(tables.serviceTokens.id, row.id))
    } catch {
      // swallow — telemetry, not auth
    }
  }

  return { userId: row.userId, sessionId: 'svc:' + row.id, roles: parseRoles(row.roles) }
}

export async function listServiceTokensForUser<S extends Record<string, unknown>>(
  db: Querier<S>,
  tables: { serviceTokens: typeof serviceTokens },
  userId: string,
): Promise<Array<{
  id: string
  label: string
  createdAt: Date
  expiresAt: Date | null
  lastUsedAt: Date | null
  status: string
}>> {
  return db
    .select({
      id: tables.serviceTokens.id,
      label: tables.serviceTokens.label,
      createdAt: tables.serviceTokens.createdAt,
      expiresAt: tables.serviceTokens.expiresAt,
      lastUsedAt: tables.serviceTokens.lastUsedAt,
      status: tables.serviceTokens.status,
    })
    .from(tables.serviceTokens)
    .where(and(eq(tables.serviceTokens.userId, userId), eq(tables.serviceTokens.status, 'active')))
}

export async function revokeServiceToken<S extends Record<string, unknown>>(
  db: Querier<S>,
  serviceTokensTable: typeof serviceTokens,
  id: string,
): Promise<void> {
  await db.update(serviceTokensTable).set({ status: 'revoked' }).where(eq(serviceTokensTable.id, id))
}

export async function revokeServiceTokensForUser<S extends Record<string, unknown>>(
  db: Querier<S>,
  serviceTokensTable: typeof serviceTokens,
  userId: string,
): Promise<void> {
  await db.update(serviceTokensTable).set({ status: 'revoked' }).where(eq(serviceTokensTable.userId, userId))
}
