const ID_PATTERN = /^[a-z0-9][a-z0-9._:@-]{0,127}$/;
const TENANT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/;
const SCOPE_PATTERN = /^[a-z][a-z0-9._:-]{0,127}$/;

/** Stable Mosaic identity. It intentionally contains no runtime/provider session identifier. */
export interface LogicalAgentIdentity {
  readonly tenantId: string;
  readonly logicalAgentId: string;
}

export interface LogicalAgentBinding {
  readonly identity: LogicalAgentIdentity;
  readonly bindingId: string;
}

export interface ConnectorLease extends LogicalAgentBinding {
  readonly leaseId: string;
  readonly connectorId: string;
  readonly scopes: readonly string[];
  readonly leaseEpoch: string;
  readonly acquiredAt: string;
  readonly heartbeatAt: string;
  readonly expiresAt: string;
  readonly releasedAt?: string;
}

export interface AcquireConnectorLeaseInput {
  readonly identity: LogicalAgentIdentity;
  readonly bindingId: string;
  readonly connectorId: string;
  readonly scopes: readonly string[];
  readonly ttlMs: number;
  readonly correlationId: string;
}

export interface TakeoverConnectorLeaseInput extends AcquireConnectorLeaseInput {
  readonly expectedEpoch: string;
}

export interface HeartbeatConnectorLeaseInput {
  readonly lease: ConnectorLease;
  readonly ttlMs: number;
  readonly correlationId: string;
}

export interface ReleaseConnectorLeaseInput {
  readonly lease: ConnectorLease;
  readonly correlationId: string;
}

export interface IssueConnectorExecutionGrantInput {
  readonly lease: ConnectorLease;
  readonly scopes: readonly string[];
  readonly ttlMs: number;
  readonly correlationId: string;
}

/** Internal server grant. Object provenance is checked in addition to these fields. */
export interface ConnectorExecutionGrant extends LogicalAgentBinding {
  readonly leaseId: string;
  readonly connectorId: string;
  readonly scopes: readonly string[];
  readonly leaseEpoch: string;
  readonly issuedAt: string;
  readonly expiresAt: string;
  readonly correlationId: string;
}

/** Normalized context passed to an adapter only after current-lease validation. */
export interface ConnectorExecutionContext extends LogicalAgentBinding {
  readonly leaseId: string;
  readonly connectorId: string;
  readonly scopes: readonly string[];
  readonly leaseEpoch: string;
  readonly correlationId: string;
  readonly grantExpiresAt: string;
}

export interface FencedConnectorAdapter<TInput, TOutput> {
  execute(input: TInput, context: ConnectorExecutionContext): Promise<TOutput>;
}

export type ConnectorLeaseAuditEventType =
  | 'acquire'
  | 'renew'
  | 'takeover'
  | 'reject'
  | 'release'
  | 'expiry';
export type ConnectorLeaseAuditOutcome = 'succeeded' | 'denied';
export type ConnectorLeaseRejectReason =
  | 'policy_denied'
  | 'lease_held'
  | 'takeover_required'
  | 'cas_mismatch'
  | 'lease_missing'
  | 'lease_released'
  | 'lease_expired'
  | 'stale_epoch'
  | 'connector_mismatch'
  | 'scope_denied'
  | 'forged_grant'
  | 'grant_expired';

/** Credential-safe metadata only: no grant object, scope set, payload, token, or approval ref. */
export interface ConnectorLeaseAuditEvent extends LogicalAgentBinding {
  readonly event: ConnectorLeaseAuditEventType;
  readonly outcome: ConnectorLeaseAuditOutcome;
  readonly connectorId: string;
  readonly correlationId: string;
  readonly occurredAt: string;
  readonly leaseId?: string;
  readonly leaseEpoch?: string;
  readonly reason?: ConnectorLeaseRejectReason;
}

export interface ConnectorLeaseAcquireMutation extends AcquireConnectorLeaseInput {
  readonly leaseId: string;
  readonly now: string;
  readonly expiresAt: string;
}

export interface ConnectorLeaseTakeoverMutation extends TakeoverConnectorLeaseInput {
  readonly leaseId: string;
  readonly now: string;
  readonly expiresAt: string;
}

export interface ConnectorLeaseHeartbeatMutation extends HeartbeatConnectorLeaseInput {
  readonly now: string;
  readonly expiresAt: string;
}

export interface ConnectorLeaseReleaseMutation extends ReleaseConnectorLeaseInput {
  readonly now: string;
}

export interface ConnectorLeaseStore {
  acquire(input: ConnectorLeaseAcquireMutation): Promise<ConnectorLease>;
  takeover(input: ConnectorLeaseTakeoverMutation): Promise<ConnectorLease>;
  heartbeat(input: ConnectorLeaseHeartbeatMutation): Promise<ConnectorLease>;
  release(input: ConnectorLeaseReleaseMutation): Promise<void>;
  findCurrent(binding: LogicalAgentBinding): Promise<ConnectorLease | null>;
  recordAudit(event: ConnectorLeaseAuditEvent): Promise<void>;
}

export function normalizeLogicalAgentIdentity(input: LogicalAgentIdentity): LogicalAgentIdentity {
  const tenantId = requiredIdentifier(input.tenantId, 'tenant ID', TENANT_PATTERN, false);
  const logicalAgentId = requiredIdentifier(
    input.logicalAgentId,
    'logical agent ID',
    ID_PATTERN,
    true,
  );
  return Object.freeze({ tenantId, logicalAgentId });
}

export function normalizeLogicalBindingId(value: string): string {
  return requiredIdentifier(value, 'logical binding ID', ID_PATTERN, true);
}

export function normalizeConnectorId(value: string): string {
  return requiredIdentifier(value, 'connector ID', ID_PATTERN, true);
}

export function normalizeCorrelationId(value: string): string {
  return requiredIdentifier(value, 'correlation ID', TENANT_PATTERN, false);
}

export function normalizeConnectorScope(value: string): string {
  return requiredIdentifier(value, 'connector scope', SCOPE_PATTERN, true);
}

export function normalizeConnectorScopes(values: readonly string[]): readonly string[] {
  if (values.length === 0) throw new Error('At least one connector scope is required');
  return Object.freeze(Array.from(new Set(values.map(normalizeConnectorScope))).sort());
}

export function normalizeLeaseEpoch(value: string): string {
  const trimmed = value.trim();
  if (!/^\d+$/.test(trimmed)) throw new Error('Lease epoch must be a positive decimal integer');
  const epoch = BigInt(trimmed);
  if (epoch < 1n) throw new Error('Lease epoch must be a positive decimal integer');
  return epoch.toString(10);
}

function requiredIdentifier(
  value: string,
  label: string,
  pattern: RegExp,
  lowerCase: boolean,
): string {
  const trimmed = value.trim();
  const normalized = lowerCase ? trimmed.toLowerCase() : trimmed;
  if (!pattern.test(normalized)) {
    throw new Error(`${label} has an invalid normalized format`);
  }
  return normalized;
}
