import type {
  ConnectionId,
  CredentialMutationAuthority,
  CredentialReferenceId,
  PrincipalId,
} from "@awp/contracts";

export type CredentialAuthorityStatus = "active" | "handoff" | "fenced";

export interface CredentialAuthority {
  readonly connectionId: ConnectionId;
  readonly credentialReferenceId: CredentialReferenceId;
  readonly ownerId: PrincipalId;
  readonly pendingOwnerId?: PrincipalId;
  readonly generation: number;
  readonly status: CredentialAuthorityStatus;
  readonly revision: number;
}

export type CredentialAuthorityConflictCode =
  | "CREDENTIAL_AUTHORITY_ALREADY_CLAIMED"
  | "STALE_CREDENTIAL_AUTHORITY"
  | "INVALID_CREDENTIAL_AUTHORITY_HANDOFF";

export class CredentialAuthorityConflictError extends Error {
  constructor(
    readonly code: CredentialAuthorityConflictCode,
    message: string,
  ) {
    super(message);
    this.name = "CredentialAuthorityConflictError";
  }
}

export function credentialMutationAuthority(
  authority: CredentialAuthority,
): CredentialMutationAuthority {
  if (authority.status !== "active" && authority.status !== "handoff") {
    throw new CredentialAuthorityConflictError(
      "STALE_CREDENTIAL_AUTHORITY",
      `Credential authority is ${authority.status}, not active`,
    );
  }
  return {
    connectionId: authority.connectionId,
    credentialReferenceId: authority.credentialReferenceId,
    ownerId: authority.ownerId,
    generation: authority.generation,
  };
}

export function assertCredentialMutationAuthority(
  authority: CredentialAuthority,
  expected: CredentialMutationAuthority,
): void {
  if (
    (authority.status !== "active" && authority.status !== "handoff") ||
    authority.connectionId !== expected.connectionId ||
    authority.credentialReferenceId !== expected.credentialReferenceId ||
    authority.ownerId !== expected.ownerId ||
    authority.generation !== expected.generation
  ) {
    throw new CredentialAuthorityConflictError(
      "STALE_CREDENTIAL_AUTHORITY",
      "Credential mutation authority is stale, fenced, or owned by another principal",
    );
  }
}
