import {
  DEFAULT_ACCESS_ACTION,
  PLATFORM_ADMIN_ACCESS_KEY,
  deriveAccessKeyCatalog,
  findAccessCatalogEntry,
  normalizeAccessKey,
  type AccessKeyCatalog,
  type AccessKeyInput,
  type NormalizedAccessKey
} from './access-keys'
import { getSystem } from '../organization-model/helpers'
import type { OrganizationModel, OrganizationModelSystemLifecycle } from '../organization-model/types'

export type AccessRestrictedBy =
  | 'catalog'
  | 'membership'
  | 'system-lifecycle'
  | 'role-permission'
  | 'diagnostic-allowlist'
  | null

export type AccessReason =
  | 'allowed'
  | 'platform-admin-bypass'
  | 'invalid-access-key'
  | 'unknown-access-key'
  | 'organization-mismatch'
  | 'missing-membership'
  | 'system-not-active'
  | 'role-permission-denied'
  | 'diagnostic-key-not-allowed'

export interface AccessAnswer {
  allowed: boolean
  restrictedBy: AccessRestrictedBy
  reason: AccessReason
}

export interface AccessContextMembership {
  id?: string
  organizationId: string
  role?: string | null
  effectivePermissions?: readonly string[] | null
}

export interface AccessContextProfile {
  id?: string
  isPlatformAdmin?: boolean | null
  is_platform_admin?: boolean | null
}

export interface AccessContext {
  organizationId: string
  organizationModel: OrganizationModel
  membership?: AccessContextMembership | null
  profile?: AccessContextProfile | null
  permissions?: readonly string[] | null
  diagnosticAllowlist?: ReadonlySet<string> | readonly string[]
  accessCatalog?: AccessKeyCatalog
  betaAccessEnabled?: boolean
  isDevelopment?: boolean
}

export interface AccessModel {
  catalog: AccessKeyCatalog
  checkAccess: (key: AccessKeyInput, ctx: Omit<AccessContext, 'accessCatalog'>) => AccessAnswer
}

const ALLOWED: AccessAnswer = { allowed: true, restrictedBy: null, reason: 'allowed' }
const PLATFORM_ADMIN_BYPASS: AccessAnswer = {
  allowed: true,
  restrictedBy: null,
  reason: 'platform-admin-bypass'
}

function deny(restrictedBy: Exclude<AccessRestrictedBy, null>, reason: Exclude<AccessReason, 'allowed'>): AccessAnswer {
  return { allowed: false, restrictedBy, reason }
}

function isPlatformAdmin(profile: AccessContextProfile | null | undefined): boolean {
  return profile?.isPlatformAdmin === true || profile?.is_platform_admin === true
}

function diagnosticAllowlistHas(
  allowlist: AccessContext['diagnosticAllowlist'],
  systemPath: string
): boolean {
  if (allowlist === undefined) return false
  return 'has' in allowlist ? allowlist.has(systemPath) : allowlist.includes(systemPath)
}

function lifecycleAllowsAccess(
  lifecycle: OrganizationModelSystemLifecycle | undefined,
  ctx: Pick<AccessContext, 'betaAccessEnabled' | 'isDevelopment'>
): boolean {
  if (lifecycle === 'active') return true
  if (lifecycle === 'beta') return ctx.betaAccessEnabled === true || ctx.isDevelopment === true
  return false
}

function getPermissionSource(ctx: AccessContext): readonly string[] | null | undefined {
  return ctx.permissions ?? ctx.membership?.effectivePermissions
}

function hasRequiredPermission(key: NormalizedAccessKey, rolePermission: string | undefined, ctx: AccessContext): boolean {
  const permissionSource = getPermissionSource(ctx)
  if (permissionSource === undefined || permissionSource === null) return true

  const requiredPermission = rolePermission ?? `${key.systemPath}.${key.action}`
  return permissionSource.includes(requiredPermission)
}

function hasExplicitRequiredPermission(rolePermission: string | undefined, ctx: AccessContext): boolean {
  const permissionSource = getPermissionSource(ctx)
  return rolePermission !== undefined && permissionSource !== undefined && permissionSource !== null
    ? permissionSource.includes(rolePermission)
    : false
}

export function checkAccess(input: AccessKeyInput, ctx: AccessContext): AccessAnswer {
  if (isPlatformAdmin(ctx.profile)) return PLATFORM_ADMIN_BYPASS

  const parsed = (() => {
    try {
      return normalizeAccessKey(input)
    } catch {
      return null
    }
  })()

  if (parsed === null) return deny('catalog', 'invalid-access-key')

  const catalog = ctx.accessCatalog ?? deriveAccessKeyCatalog(ctx.organizationModel)
  const catalogEntry = findAccessCatalogEntry(catalog, parsed)

  if (catalogEntry === undefined) return deny('catalog', 'unknown-access-key')

  const membership = ctx.membership
  if (membership === undefined || membership === null) return deny('membership', 'missing-membership')
  if (membership.organizationId !== ctx.organizationId) return deny('membership', 'organization-mismatch')

  if (parsed.systemPath === PLATFORM_ADMIN_ACCESS_KEY) {
    return deny('role-permission', 'role-permission-denied')
  }

  if (catalogEntry.source === 'diagnostic') {
    if (!diagnosticAllowlistHas(ctx.diagnosticAllowlist, parsed.systemPath)) {
      return deny('diagnostic-allowlist', 'diagnostic-key-not-allowed')
    }

    if (parsed.action !== DEFAULT_ACCESS_ACTION && !hasRequiredPermission(parsed, catalogEntry.rolePermission, ctx)) {
      return deny('role-permission', 'role-permission-denied')
    }

    return ALLOWED
  }

  if (catalogEntry.source === 'permission') {
    if (!hasExplicitRequiredPermission(catalogEntry.rolePermission, ctx)) {
      return deny('role-permission', 'role-permission-denied')
    }

    return ALLOWED
  }

  const system = getSystem(ctx.organizationModel, parsed.systemPath)
  if (system === undefined || !lifecycleAllowsAccess(system.lifecycle, ctx)) {
    return deny('system-lifecycle', 'system-not-active')
  }

  if (parsed.action !== DEFAULT_ACCESS_ACTION && !hasRequiredPermission(parsed, catalogEntry.rolePermission, ctx)) {
    return deny('role-permission', 'role-permission-denied')
  }

  return ALLOWED
}

export function createAccessModel(organizationModel: OrganizationModel): AccessModel {
  const catalog = deriveAccessKeyCatalog(organizationModel)

  return {
    catalog,
    checkAccess: (key, ctx) => checkAccess(key, { ...ctx, organizationModel, accessCatalog: catalog })
  }
}
