/**
 * T7 · Subdomain router (§4.2 routing). `extractSubdomain` returns the SINGLE leftmost label
 * of `host` under `baseDomain`, or null for a bare/nested/confusing Host (INV1 Host-confusion
 * guard). It is a HINT for candidate lookup only — NEVER the authority: T8 requires the
 * capability token's `aud` to equal this subdomain and P5 gates `token.host` against the
 * resolver's `hostId`. `RouteResolver` is IMPLEMENTED BY P3; P1 owns only the interface.
 */
export interface ResolvedHost {
  readonly hostId: string
  readonly accountId: string
  readonly subdomain: string
}

export interface RouteResolver {
  resolveSubdomain(subdomain: string): Promise<ResolvedHost | null>
}

const LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/

/** Strip a single trailing dot and lowercase; strip a `:port` suffix if present. */
function normalizeHost(host: string): string {
  const noPort = host.split(':', 1)[0] ?? host
  const trimmed = noPort.endsWith('.') ? noPort.slice(0, -1) : noPort
  return trimmed.toLowerCase()
}

/**
 * Extract the single tenant label: `'alice.term.example.com'` under `'term.example.com'` → `'alice'`.
 * Returns null when: host equals the base domain (bare), the suffix does not match, or the prefix
 * is multi-label (`'a.b.term.example.com'` → null — nested subdomains are rejected, INV1).
 */
export function extractSubdomain(hostHeader: string, baseDomain: string): string | null {
  const host = normalizeHost(hostHeader)
  const base = normalizeHost(baseDomain)
  if (host === base) return null // bare base domain — no tenant
  const suffix = '.' + base
  if (!host.endsWith(suffix)) return null // confusion attempt (e.g. '...example.com.evil.com')
  const label = host.slice(0, host.length - suffix.length)
  if (label.length === 0) return null
  if (label.includes('.')) return null // multi-label prefix rejected (single-label tenant only)
  if (!LABEL_RE.test(label)) return null // invalid DNS label
  return label
}
