import type { Repository } from '../../db/repository.js';
import { resolveOrgIds } from '../auth.js';

export interface A2aDelegation {
  /** Primary key of the a2a_delegations row. */
  id: string;
  userId: string;
  clientId: string;
  grantId: string;
  grantedSpaceIds: string[];
  grantedSkills: string[];
  expiresAt: string | null;
  revokedAt: string | null;
}

/** 委任が有効か（失効していない & 期限内）。nowIso は呼び出し側が渡す（テスト決定論のため）。 */
export function isDelegationLive(
  d: { expiresAt: string | null; revokedAt: string | null },
  nowIso: string,
): boolean {
  if (d.revokedAt) return false;
  if (d.expiresAt && new Date(d.expiresAt).getTime() <= new Date(nowIso).getTime()) return false;
  return true;
}

/**
 * computeEffectiveScope に渡せる最小限の principal 型。
 * A2aPrincipal (token-auth.ts) はこのインターフェースを満たす。
 * 循環 import を避けるためここで独立定義。
 */
export interface A2aPrincipalLike {
  actingUserId: string;
  /** OAuth クライアント識別子。audit log 記録用（省略可）。 */
  clientId?: string;
  delegation: {
    grantedSpaceIds: string[];
    grantedSkills: string[];
  };
}

export interface EffectiveScope {
  viewer: Express.User;
  userVisibleSpaceIds: string[];
  allowlistsBySpace: Record<string, string[]>;
  effectiveSpaceIds: string[];
  effectiveSkills: string[];
}

/**
 * acting user の実際の可視スペース・スキルを delegation の AND 交差で絞り込む。
 * user が存在しないまたは active でない場合は null を返す（fail-closed）。
 */
export async function computeEffectiveScope(
  repo: Repository,
  principal: A2aPrincipalLike,
): Promise<EffectiveScope | null> {
  const u = repo.getUserById(principal.actingUserId);
  if (!u || u.status !== 'active') return null;
  const viewer = {
    id: u.id,
    role: u.role,
    orgIds: resolveOrgIds(repo, u.id),
    status: u.status,
    name: u.name,
  } as Express.User;
  const visible = await repo.listSpaces({ viewer });
  const userVisibleSpaceIds = visible.map(s => s.id);
  const allowlistsBySpace: Record<string, string[]> = {};
  for (const s of visible) {
    allowlistsBySpace[s.id] = repo.getSpaceA2aSkills(s.id);
  }
  const effectiveSpaceIds = resolveEffectiveSpaces(
    principal.delegation.grantedSpaceIds,
    userVisibleSpaceIds,
  );
  const effectiveSkills = resolveEffectiveSkills(
    principal.delegation.grantedSkills,
    allowlistsBySpace,
    effectiveSpaceIds,
  );
  return { viewer, userVisibleSpaceIds, allowlistsBySpace, effectiveSpaceIds, effectiveSkills };
}

/** AND 交差: 委任が許可したスペース ∩ acting user が実際に見えるスペース。 */
export function resolveEffectiveSpaces(grantedSpaceIds: string[], userVisibleSpaceIds: string[]): string[] {
  const visible = new Set(userVisibleSpaceIds);
  return [...new Set(grantedSpaceIds)].filter(id => visible.has(id));
}

/** AND 交差: 委任スキル ∩ （有効スペースの A2A 許可リストの和集合）。重複排除。 */
export function resolveEffectiveSkills(
  grantedSkills: string[],
  spaceAllowlistsBySpace: Record<string, string[]>,
  effectiveSpaceIds: string[],
): string[] {
  const allowed = new Set<string>();
  for (const sid of effectiveSpaceIds) {
    for (const skill of (spaceAllowlistsBySpace[sid] ?? [])) allowed.add(skill);
  }
  const grantedSet = new Set(grantedSkills);
  return [...allowed].filter(s => grantedSet.has(s));
}
