// requirePermission runs application-level authorisation only after trusted
// middleware has cryptographically verified identity. It cannot establish trust.

export type VerifiedPrincipal = {
  subject: string;
  permissions: readonly string[];
};

export function requirePermission(
  principal: VerifiedPrincipal | null,
  permission: string,
): void {
  if (principal === null || principal.subject.trim().length === 0) {
    throw new Error("Unauthenticated");
  }
  if (permission.trim().length === 0) {
    throw new Error("Forbidden");
  }
  if (!principal.permissions.includes(permission)) {
    throw new Error("Forbidden");
  }
}

// canPromote evaluates promotion evidence from trusted CI/approval systems;
// it is the decision rule, not the system authenticating evidence.

export type PromotionEvidence = {
  testedDigest: string;
  proposedDigest: string;
  checksPassed: boolean;
  approvedDigest: string;
};

function isValidSha256(digest: string): boolean {
  return /^sha256:[a-f0-9]{64}$/.test(digest);
}

export function canPromote(evidence: PromotionEvidence): boolean {
  if (!isValidSha256(evidence.testedDigest)) return false;
  if (evidence.checksPassed !== true) return false;
  if (evidence.proposedDigest !== evidence.testedDigest) return false;
  if (evidence.approvedDigest !== evidence.testedDigest) return false;
  return true;
}

// acceptResearchResult validates raw research data. Source admission is NOT
// verification that a cited page supports the summary.

export type ResearchResult = {
  taskId: string;
  summary: string;
  sources: readonly { url: string; title: string }[];
};

const ROOT_KEYS = new Set(["taskId", "summary", "sources"]);
const SRC_KEYS = new Set(["url", "title"]);

export function acceptResearchResult(
  raw: unknown,
  expectedTaskId: string,
  permittedSourceUrls: ReadonlySet<string>,
): ResearchResult {
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
    throw new Error("Invalid research result");
  }
  const obj = raw as Record<string, unknown>;
  if (Object.keys(obj).some((k) => !ROOT_KEYS.has(k))) {
    throw new Error("Invalid research result");
  }
  const taskId = obj.taskId;
  if (typeof taskId !== "string" || taskId.trim() === "" || taskId !== expectedTaskId) {
    throw new Error("Invalid research result");
  }
  const summary = obj.summary;
  if (typeof summary !== "string" || summary.trim().length === 0 || summary.length > 2000) {
    throw new Error("Invalid research result");
  }
  const sourcesRaw = obj.sources;
  if (!Array.isArray(sourcesRaw) || sourcesRaw.length < 1 || sourcesRaw.length > 10) {
    throw new Error("Invalid research result");
  }
  const seen = new Set<string>();
  const sources: Array<ResearchResult["sources"][number]> = [];
  for (const src of sourcesRaw) {
    if (src === null || typeof src !== "object" || Array.isArray(src)) {
      throw new Error("Invalid research result");
    }
    const s = src as Record<string, unknown>;
    if (Object.keys(s).some((k) => !SRC_KEYS.has(k))) {
      throw new Error("Invalid research result");
    }
    const title = s.title;
    if (typeof title !== "string" || title.trim().length === 0 || title.length > 200) {
      throw new Error("Invalid research result");
    }
    const url = s.url;
    if (typeof url !== "string" || !permittedSourceUrls.has(url)) {
      throw new Error("Invalid research result");
    }
    if (seen.has(url)) {
      throw new Error("Invalid research result");
    }
    let parsed: URL;
    try {
      parsed = new URL(url);
    } catch {
      throw new Error("Invalid research result");
    }
    if (parsed.protocol !== "https:") {
      throw new Error("Invalid research result");
    }
    if (parsed.username !== "" || parsed.password !== "") {
      throw new Error("Invalid research result");
    }
    seen.add(url);
    sources.push({ url, title });
  }
  return { taskId, summary, sources };
}

// taskIngress generates ONE CloudFormation security group ingress resource for
// an HTTPS task listener — not a complete network security configuration.

const SG_RE = /^sg-[a-f0-9]{8}([a-f0-9]{9})?$/;

export function taskIngress(
  loadBalancerSecurityGroupId: string,
  taskSecurityGroupId: string,
) {
  if (!SG_RE.test(loadBalancerSecurityGroupId) || !SG_RE.test(taskSecurityGroupId)) {
    throw new Error("Invalid security group");
  }
  if (loadBalancerSecurityGroupId === taskSecurityGroupId) {
    throw new Error("Invalid security group");
  }
  return {
    Type: "AWS::EC2::SecurityGroupIngress",
    Properties: {
      GroupId: taskSecurityGroupId,
      SourceSecurityGroupId: loadBalancerSecurityGroupId,
      IpProtocol: "tcp",
      FromPort: 443,
      ToPort: 443,
    },
  };
}
