import { useAuth } from 'react-oidc-context'

/**
 * Permission helpers для schema + record workflow. Phase 1d — **ACTIVE**:
 * декодирует access_token, читает {@code realm_access.roles} +
 * {@code resource_access.ordinis.roles} и гейтит UI actions.
 *
 * <p>Naming convention: colon-separated {@code ordinis:<domain>:<action>}
 * pattern (consistent с scope ACL {@code ordinis:client:public}). Backend
 * parser в {@code WorkflowRoles.java}.
 *
 * <p>Backend enforcement (см. {@code WorkflowRoles}):
 * <ul>
 *   <li>{@code 403 forbidden_role} — actor не имеет требуемой realm role.</li>
 *   <li>{@code 403 self_approve_forbidden} — actor == maker (maker-checker).</li>
 * </ul>
 *
 * <p>Frontend hide'ит соответствующие кнопки на UI level для UX, backend
 * гейт остаётся primary defence. Если user имеет роль в Keycloak —
 * кнопки видны и действия пройдут backend; если не имеет — кнопки скрыты,
 * backend всё равно 403'нет на прямой API call.
 *
 * <p><b>Migration note (Phase 1d enforcement):</b> Keycloak realm `nstart`
 * должен иметь созданные роли:
 * <ul>
 *   <li>{@code ordinis:schema:reviewer} — schema decide</li>
 *   <li>{@code ordinis:schema:publisher} — schema publish</li>
 *   <li>{@code ordinis:record:reviewer} — record approve/reject</li>
 * </ul>
 * и actual reviewer-users должны их получить. До этого все decide /
 * publish операции вернут {@code 403 forbidden_role}.
 */

export const ROLE_SCHEMA_REVIEWER = 'ordinis:schema:reviewer'
export const ROLE_SCHEMA_PUBLISHER = 'ordinis:schema:publisher'
export const ROLE_RECORD_REVIEWER = 'ordinis:record:reviewer'
export const ROLE_ADMIN = 'ordinis:admin'

/**
 * Декодирует payload JWT без верификации подписи (для чтения claims).
 *
 * <p>Безопасно для UI gate'ов: подпись проверяет Keycloak при выдаче, backend
 * — при каждом API вызове через {@code spring-security-oauth2-resource-server}.
 * Frontend читает claims только чтобы решить «показать кнопку или нет»;
 * primary defence остаётся на backend.
 *
 * <p>JWT формат: {@code header.payload.signature}, все три части
 * base64url-encoded. Берём payload, normalize base64url → base64, decode,
 * parse JSON.
 */
function decodeJwtPayload(token: string | undefined | null): Record<string, unknown> | null {
  if (!token || typeof token !== 'string') return null
  const parts = token.split('.')
  if (parts.length !== 3) return null
  try {
    // base64url → base64: + / padding
    const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/')
    const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4))
    const json = atob(b64 + pad)
    // UTF-8 декодирование (atob возвращает binary string, нужен TextDecoder
    // если есть non-ASCII в claims — например русское имя в name claim).
    const bytes = new Uint8Array(json.length)
    for (let i = 0; i < json.length; i++) bytes[i] = json.charCodeAt(i)
    const decoded = new TextDecoder('utf-8').decode(bytes)
    return JSON.parse(decoded) as Record<string, unknown>
  } catch {
    return null
  }
}

/**
 * Достаёт roles из access_token. Поддерживает оба claim path:
 * - {@code realm_access.roles} — Keycloak realm-роли
 * - {@code resource_access.ordinis.roles} — Keycloak client-роли (scoped к
 *   {@code ordinis} client)
 *
 * <p><b>Важно — почему access_token, не id_token:</b> Keycloak по дефолту
 * mapper'ит {@code realm_access} / {@code resource_access} только в
 * access_token. ID token (который {@code auth.user.profile} в oidc-client-ts)
 * содержит только стандартные OIDC claims (sub, email, name, preferred_username).
 * Раньше код читал {@code profile} → roles всегда пустой → "Согласования"
 * скрыт даже у admin'а. Fix: decode access_token напрямую.
 *
 * <p>Backend читает один из двух path через
 * {@code OrdinisAuthProperties.rolesClaim} (default {@code realm_access.roles},
 * на prod/staging {@code resource_access.ordinis.roles}). Frontend читает
 * оба и объединяет — UI gate должен работать независимо от config'а.
 */
function tokenRoles(accessToken: string | undefined | null): string[] {
  const payload = decodeJwtPayload(accessToken)
  if (!payload) return []
  const p = payload as {
    realm_access?: { roles?: unknown }
    resource_access?: { ordinis?: { roles?: unknown } }
  }
  const fromRealm = Array.isArray(p.realm_access?.roles)
    ? (p.realm_access.roles as unknown[]).filter((r): r is string => typeof r === 'string')
    : []
  const fromClient = Array.isArray(p.resource_access?.ordinis?.roles)
    ? (p.resource_access.ordinis.roles as unknown[]).filter(
        (r): r is string => typeof r === 'string',
      )
    : []
  return [...fromRealm, ...fromClient]
}

/**
 * True если у actor'а есть указанная role или super-role {@link ROLE_ADMIN}.
 * Admin grants full workflow access — convenience override для small teams.
 */
function hasRole(accessToken: string | undefined | null, role: string): boolean {
  const roles = tokenRoles(accessToken)
  return roles.includes(role) || roles.includes(ROLE_ADMIN)
}

/** Может ли актор approve / request_changes / reject schema draft. */
export function useCanReviewSchema(): boolean {
  const auth = useAuth()
  return auth.isAuthenticated && hasRole(auth.user?.access_token, ROLE_SCHEMA_REVIEWER)
}

/** Может ли актор approve / reject record draft. */
export function useCanReviewRecord(): boolean {
  const auth = useAuth()
  return auth.isAuthenticated && hasRole(auth.user?.access_token, ROLE_RECORD_REVIEWER)
}

/** Может ли актор publish approved schema draft (bump live version). */
export function useCanPublishSchema(): boolean {
  const auth = useAuth()
  return auth.isAuthenticated && hasRole(auth.user?.access_token, ROLE_SCHEMA_PUBLISHER)
}

/**
 * Может ли актор create schema draft (maker side). Maker не gated
 * специальной ролью — любой authenticated с required scope. Backend
 * проверяет scope ACL для конкретного словаря.
 */
export function useCanCreateSchemaDraft(): boolean {
  const auth = useAuth()
  return auth.isAuthenticated
}
