/**
 * Authorizer adapter — implements the term-relay (P1) `Authorizer` port by DELEGATING every verdict
 * to the REAL relay-auth (P5) `onUpgrade`/`onReattach`. P1 holds no authz logic; this file only
 * reconciles two structurally-drifted-but-equivalent seam shapes:
 *
 *   1. DpopContext. P1's port carries `{ proof, publicKeyThumbprint }`; P5 needs `{ proofJws, htu,
 *      htm }`. The relay derives `htu`/`htm` from the request's OWN authority (`expectedAud` = the
 *      resolved subdomain), never from client-supplied claims — the spec-faithful DPoP binding.
 *   2. AuthzOutcome. P5's `principal` is a full AuthenticatedPrincipal; P1's port only needs a
 *      string, so we surface `principal.accountId` (P1's adapter reads only `hostId`/`jti`).
 *
 * F2 (step-up principal wiring): Phase 0 uses a not-required step-up policy, so P5 resolves identity
 * from the signed token (INV3) and a null session-principal is correct. `resolvePrincipal` is the
 * seam a later phase injects to pass an authenticated principal for step-up-required hosts.
 */
import {
  onUpgrade as p5OnUpgrade,
  onReattach as p5OnReattach,
  type EnforceDeps,
  type AuthenticatedPrincipal,
  type AuthzOutcome as P5AuthzOutcome,
} from 'relay-auth'
import type {
  Authorizer,
  UpgradeContext as PortUpgradeContext,
  AuthzOutcome as PortAuthzOutcome,
} from 'term-relay/data-plane/authz-port.js'
import { DPOP_HTM, htuFor } from './capability.js'

export interface AuthorizerDeps {
  readonly deps: EnforceDeps
  readonly allowedOrigins: readonly string[]
  readonly now: () => number
  /** F2 seam: resolve the authenticated session principal for step-up. Phase 0 default → null. */
  readonly resolvePrincipal?: (ctx: PortUpgradeContext) => AuthenticatedPrincipal | null
}

function toP5Context(
  ctx: PortUpgradeContext,
  resolvePrincipal: (ctx: PortUpgradeContext) => AuthenticatedPrincipal | null,
) {
  return {
    capabilityRaw: ctx.capabilityRaw,
    originHeader: ctx.originHeader,
    expectedAud: ctx.expectedAud,
    requestedHostId: ctx.requestedHostId,
    requiredRight: ctx.requiredRight,
    remoteAddrHash: ctx.remoteAddrHash,
    activeSessionCount: ctx.activeSessionCount,
    // Reconstruct the full DPoP context; htu/htm come from the relay's authority, not the client.
    dpop: { proofJws: ctx.dpop.proof ?? '', htu: htuFor(ctx.expectedAud), htm: DPOP_HTM },
    principal: resolvePrincipal(ctx),
  }
}

function toPortOutcome(outcome: P5AuthzOutcome): PortAuthzOutcome {
  return outcome.ok
    ? { ok: true, hostId: outcome.hostId, principal: outcome.principal.accountId, jti: outcome.jti }
    : { ok: false, status: outcome.status }
}

/** Build the P1 `Authorizer` port backed by the real P5 enforcement pipeline. */
export function createAuthorizer(deps: AuthorizerDeps): Authorizer {
  const resolvePrincipal = deps.resolvePrincipal ?? (() => null)
  return {
    async onUpgrade(ctx, now) {
      const outcome = await p5OnUpgrade(
        toP5Context(ctx, resolvePrincipal),
        deps.deps,
        deps.allowedOrigins,
        now,
      )
      return toPortOutcome(outcome)
    },
    async onReattach(ctx, now) {
      const outcome = await p5OnReattach(
        { ...toP5Context(ctx, resolvePrincipal), sessionId: ctx.sessionId },
        deps.deps,
        deps.allowedOrigins,
        now,
      )
      return toPortOutcome(outcome)
    },
  }
}
