import type {
  AgentRunId,
  Connection,
  CredentialReference,
  AttemptId,
  AuditRecord,
  BusinessEvent,
  ChangeSetId,
  MachineId,
  MachineEnrollmentId,
  ClusterId,
  ConfigurationDefinition,
  ConfigurationOverride,
  ConfigurationScope,
  ConnectionId,
  CredentialReferenceId,
  FactoryRunId,
  GoalId,
  OperationId,
  OutboxMessageId,
  PlanId,
  PlanRevisionId,
  PlanningSessionId,
  ProjectId,
  ProjectVisionVersionId,
  ProviderReference,
  PrincipalId,
  ReviewId,
  ReviewFindingId,
  SessionId,
  VerificationEvidenceId,
  TaskId,
  WorkspaceId,
} from "@awp/contracts";
import type {
  AgentRun,
  Attempt,
  ChangeSet,
  MachineEnrollment,
  MachineCapability,
  Machine,
  Cluster,
  CredentialAuthority,
  FactoryRun,
  Goal,
  Plan,
  PlanRevision,
  PlanningSession,
  ProjectPlanningDefaults,
  Project,
  ProjectVisionVersion,
  Review,
  ReviewFinding,
  VerificationEvidence,
  Task,
  Workspace,
} from "@awp/domain";

export interface EntityRepository<T, Id> {
  getById(id: Id): Promise<T | undefined>;
  insert(entity: T): Promise<void>;
  update(entity: T): Promise<void>;
}

export interface ClusterRepository extends EntityRepository<Cluster, ClusterId> {
  list(): Promise<readonly Cluster[]>;
}
export interface MachineRepository extends EntityRepository<Machine, MachineId> {
  listByCluster(clusterId: ClusterId): Promise<readonly Machine[]>;
}
export interface MachineCapabilityRepository {
  listByMachine(machineId: MachineId): Promise<readonly MachineCapability[]>;
  replaceForMachine(
    machineId: MachineId,
    capabilities: readonly MachineCapability[],
  ): Promise<void>;
}
export interface MachineEnrollmentRepository extends EntityRepository<
  MachineEnrollment,
  MachineEnrollmentId
> {
  getByOperationId(operationId: OperationId): Promise<MachineEnrollment | undefined>;
  listByMachine(machineId: MachineId): Promise<readonly MachineEnrollment[]>;
}
export interface ConnectionRepository {
  getById(id: ConnectionId): Promise<Connection | undefined>;
  upsert(value: Connection): Promise<void>;
}
export interface CredentialReferenceRepository {
  getById(id: CredentialReferenceId): Promise<CredentialReference | undefined>;
  upsert(value: CredentialReference): Promise<void>;
}

export interface ProjectRepository extends EntityRepository<Project, ProjectId> {
  list(): Promise<readonly Project[]>;
}
export interface ProjectVisionRepository {
  getById(id: ProjectVisionVersionId): Promise<ProjectVisionVersion | undefined>;
  getCurrent(projectId: ProjectId): Promise<ProjectVisionVersion | undefined>;
  insert(entity: ProjectVisionVersion): Promise<void>;
}
export interface GoalRepository extends EntityRepository<Goal, GoalId> {
  listByProject(projectId: ProjectId): Promise<readonly Goal[]>;
}
export interface PlanRepository extends EntityRepository<Plan, PlanId> {
  listByProject(projectId: ProjectId): Promise<readonly Plan[]>;
}
export interface PlanRevisionRepository {
  getById(id: PlanRevisionId): Promise<PlanRevision | undefined>;
  getCurrent(planId: PlanId): Promise<PlanRevision | undefined>;
  insert(entity: PlanRevision): Promise<void>;
}
export interface PlanningSessionRepository extends EntityRepository<
  PlanningSession,
  PlanningSessionId
> {
  getByPlanId(planId: PlanId): Promise<PlanningSession | undefined>;
  listByProject(projectId: ProjectId): Promise<readonly PlanningSession[]>;
}
export interface ProjectPlanningDefaultsRepository {
  getByProjectId(projectId: ProjectId): Promise<ProjectPlanningDefaults | undefined>;
  upsert(value: ProjectPlanningDefaults): Promise<void>;
}
export interface TaskRepository extends EntityRepository<Task, TaskId> {
  listByProject(projectId: ProjectId): Promise<readonly Task[]>;
}
export interface FactoryRunRepository extends EntityRepository<FactoryRun, FactoryRunId> {
  listByProject(projectId: ProjectId): Promise<readonly FactoryRun[]>;
}
export interface AgentRunRepository extends EntityRepository<AgentRun, AgentRunId> {
  listByProject(projectId: ProjectId): Promise<readonly AgentRun[]>;
}
export type WorkspaceRepository = EntityRepository<Workspace, WorkspaceId>;
export interface AttemptRepository extends EntityRepository<Attempt, AttemptId> {
  listByAgentRunIds(agentRunIds: readonly AgentRunId[]): Promise<readonly Attempt[]>;
  bindProviderReference(attemptId: AttemptId, reference: ProviderReference): Promise<Attempt>;
}
export interface ChangeSetRepository extends EntityRepository<ChangeSet, ChangeSetId> {
  listByProject(projectId: ProjectId): Promise<readonly ChangeSet[]>;
}
export interface ReviewRepository extends EntityRepository<Review, ReviewId> {
  listByChangeSetIds(changeSetIds: readonly ChangeSetId[]): Promise<readonly Review[]>;
}
export interface VerificationEvidenceRepository extends EntityRepository<
  VerificationEvidence,
  VerificationEvidenceId
> {
  listByChangeSetIds(
    changeSetIds: readonly ChangeSetId[],
  ): Promise<readonly VerificationEvidence[]>;
}
export interface ReviewFindingRepository extends EntityRepository<ReviewFinding, ReviewFindingId> {
  listByChangeSetIds(changeSetIds: readonly ChangeSetId[]): Promise<readonly ReviewFinding[]>;
}

export interface SessionRecord {
  readonly id: SessionId;
  readonly tokenHash: string;
  readonly principalId: PrincipalId;
  readonly credentialVersionDigest: string;
  readonly createdAt: string;
  readonly lastSeenAt: string;
  readonly expiresAt: string;
  readonly revokedAt?: string;
  readonly userAgentDigest?: string;
}
export interface SessionRepository {
  getById(id: SessionId): Promise<SessionRecord | undefined>;
  getByTokenHash(tokenHash: string): Promise<SessionRecord | undefined>;
  insert(session: SessionRecord): Promise<void>;
  updateLastSeen(id: SessionId, lastSeenAt: string): Promise<void>;
  revoke(id: SessionId, revokedAt: string): Promise<void>;
  revokeAllByPrincipal(principalId: PrincipalId, revokedAt: string): Promise<number>;
}

export interface ConfigurationDefinitionRepository {
  getByKey(key: string): Promise<ConfigurationDefinition<unknown> | undefined>;
  list(): Promise<readonly ConfigurationDefinition<unknown>[]>;
  insert(definition: ConfigurationDefinition<unknown>): Promise<void>;
  replace(definition: ConfigurationDefinition<unknown>): Promise<void>;
}

export interface ConfigurationOverrideRepository {
  get(
    definitionKey: string,
    scopeType: ConfigurationScope,
    scopeId: string,
  ): Promise<ConfigurationOverride<unknown> | undefined>;
  listByDefinition(definitionKey: string): Promise<readonly ConfigurationOverride<unknown>[]>;
  upsert(override: ConfigurationOverride<unknown>): Promise<void>;
  delete(definitionKey: string, scopeType: ConfigurationScope, scopeId: string): Promise<void>;
}

export interface CredentialAuthorityRepository {
  getByConnectionId(connectionId: ConnectionId): Promise<CredentialAuthority | undefined>;
  claim(input: {
    readonly connectionId: ConnectionId;
    readonly credentialReferenceId: CredentialReferenceId;
    readonly ownerId: PrincipalId;
  }): Promise<CredentialAuthority>;
  prepareHandoff(input: {
    readonly connectionId: ConnectionId;
    readonly credentialReferenceId: CredentialReferenceId;
    readonly ownerId: PrincipalId;
    readonly nextOwnerId: PrincipalId;
    readonly expectedGeneration: number;
  }): Promise<CredentialAuthority>;
  fence(input: {
    readonly connectionId: ConnectionId;
    readonly credentialReferenceId: CredentialReferenceId;
    readonly ownerId: PrincipalId;
    readonly expectedGeneration: number;
  }): Promise<CredentialAuthority>;
  transfer(input: {
    readonly connectionId: ConnectionId;
    readonly credentialReferenceId: CredentialReferenceId;
    readonly ownerId: PrincipalId;
    readonly nextOwnerId: PrincipalId;
    readonly expectedGeneration: number;
  }): Promise<CredentialAuthority>;
}

export interface BusinessEventRepository {
  append(event: BusinessEvent): Promise<void>;
  listByProject(projectId: ProjectId): Promise<readonly BusinessEvent[]>;
}
export interface AuditRepository {
  append(record: AuditRecord): Promise<void>;
}
export interface OutboxMessage {
  readonly id: OutboxMessageId;
  readonly topic: string;
  readonly payload: unknown;
  readonly occurredAt: string;
}
export interface PendingOutboxMessage extends OutboxMessage {
  readonly published: boolean;
  readonly publishedAt?: string;
  readonly attempts: number;
}
export interface OutboxRepository {
  append(message: OutboxMessage): Promise<void>;
  listPending(limit: number): Promise<readonly PendingOutboxMessage[]>;
  recordAttempt(id: OutboxMessageId): Promise<void>;
  markPublished(id: OutboxMessageId, publishedAt: string): Promise<void>;
}

export interface OutboxConsumerReceipt {
  readonly consumerId: string;
  readonly messageId: OutboxMessageId;
  readonly processedAt: string;
}

export class OutboxConsumerReceiptConflictError extends Error {
  constructor(
    readonly consumerId: string,
    readonly messageId: OutboxMessageId,
  ) {
    super(`Outbox message ${messageId} is already processed by ${consumerId}`);
    this.name = "OutboxConsumerReceiptConflictError";
  }
}

export interface OutboxConsumerReceiptRepository {
  get(consumerId: string, messageId: OutboxMessageId): Promise<OutboxConsumerReceipt | undefined>;
  insert(receipt: OutboxConsumerReceipt): Promise<void>;
}

export interface WorkflowStepOutcome {
  readonly operationId: OperationId;
  readonly stepName: string;
  readonly stepKey: string;
  readonly outcome: unknown;
  readonly recordedAt: string;
}

export class WorkflowStepOutcomeConflictError extends Error {
  constructor(
    readonly operationId: OperationId,
    readonly stepName: string,
    readonly stepKey: string,
  ) {
    super(`Workflow step outcome already exists for ${operationId}/${stepName}/${stepKey}`);
    this.name = "WorkflowStepOutcomeConflictError";
  }
}

export interface WorkflowStepOutcomeRepository {
  get(
    operationId: OperationId,
    stepName: string,
    stepKey: string,
  ): Promise<WorkflowStepOutcome | undefined>;
  insert(outcome: WorkflowStepOutcome): Promise<void>;
}

export interface ApplicationTransaction {
  readonly clusters: ClusterRepository;
  readonly machines: MachineRepository;
  readonly machineCapabilities: MachineCapabilityRepository;
  readonly machineEnrollments: MachineEnrollmentRepository;
  readonly connections: ConnectionRepository;
  readonly credentialReferences: CredentialReferenceRepository;
  readonly projects: ProjectRepository;
  readonly projectVisions: ProjectVisionRepository;
  readonly goals: GoalRepository;
  readonly plans: PlanRepository;
  readonly planRevisions: PlanRevisionRepository;
  readonly planningSessions: PlanningSessionRepository;
  readonly projectPlanningDefaults: ProjectPlanningDefaultsRepository;
  readonly tasks: TaskRepository;
  readonly factoryRuns: FactoryRunRepository;
  readonly agentRuns: AgentRunRepository;
  readonly workspaces: WorkspaceRepository;
  readonly attempts: AttemptRepository;
  readonly changeSets: ChangeSetRepository;
  readonly reviews: ReviewRepository;
  readonly verificationEvidence: VerificationEvidenceRepository;
  readonly reviewFindings: ReviewFindingRepository;
  readonly sessions: SessionRepository;
  readonly credentialAuthorities: CredentialAuthorityRepository;
  readonly configurationDefinitions?: ConfigurationDefinitionRepository;
  readonly configurationOverrides?: ConfigurationOverrideRepository;
  readonly events: BusinessEventRepository;
  readonly audit: AuditRepository;
  readonly outbox: OutboxRepository;
  readonly outboxConsumerReceipts: OutboxConsumerReceiptRepository;
  readonly workflowStepOutcomes: WorkflowStepOutcomeRepository;
}

export interface UnitOfWork {
  transaction<T>(work: (tx: ApplicationTransaction) => Promise<T>): Promise<T>;
}
