generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum UserStatus {
  ACTIVE
  SUSPENDED
  DELETED
}

enum ServerEdition {
  JAVA
  BEDROCK
}

enum SoftwareFamily {
  VANILLA
  PAPER
  PURPUR
  FABRIC
  FORGE
  NEOFORGE
  QUILT
  BEDROCK_DEDICATED
}

enum GameServerStatus {
  DRAFT
  PROVISIONING
  INSTALLING
  STOPPED
  QUEUED
  STARTING
  RUNNING
  STOPPING
  BACKING_UP
  RESTORING
  UNKNOWN
  ERROR
  DELETING
  DELETED
}

enum GameNodeStatus {
  ONLINE
  OFFLINE
  MAINTENANCE
  DRAINING
  UNREACHABLE
}

enum AllocationStatus {
  ACTIVE
  RELEASED
}

enum StartQueueStatus {
  QUEUED
  DISPATCHED
  CANCELLED
  EXPIRED
}

enum JobStatus {
  PENDING
  RUNNING
  COMPLETED
  FAILED
  CANCELLED
}

enum BackupStatus {
  PENDING
  RUNNING
  AVAILABLE
  FAILED
  DELETED
}

enum BackupType {
  MANUAL
  SCHEDULED
  PRE_RESTORE
  PRE_WORLD_REPLACE
}

enum BackupRestoreStatus {
  PENDING
  RUNNING
  COMPLETED
  FAILED
}

enum WorldUploadStatus {
  PENDING
  UPLOADING
  VALIDATING
  READY
  COMPLETED
  FAILED
}

enum AddonType {
  PLUGIN
  MOD
  MODPACK
  DATAPACK
}

enum AddonInstallStatus {
  PENDING
  INSTALLING
  INSTALLED
  FAILED
  REMOVING
  REMOVED
}

enum CreditTransactionType {
  GRANT
  USAGE_DEBIT
  ADJUSTMENT
  EXPIRE
}

enum DnsRecordStatus {
  PENDING
  ACTIVE
  FAILED
  REMOVED
}

enum WhmcsServiceStatus {
  PENDING
  ACTIVE
  SUSPENDED
  TERMINATED
}

enum IntegrationOperationStatus {
  COMPLETED
  FAILED
}

enum SsoTicketKind {
  SERVICE
  ADMIN
}

enum IntegrationEventStatus {
  PENDING
  ACKNOWLEDGED
  DEAD_LETTER
}

enum ReconciliationSeverity {
  INFO
  WARNING
  BLOCKING
  DESTRUCTIVE
  SECURITY
}

enum UsageExportStatus {
  DRAFT
  EXPORTED
  ADJUSTED
}

model User {
  id                  String     @id @default(uuid())
  email               String     @unique
  username            String     @unique
  displayName         String?
  status              UserStatus @default(ACTIVE)
  emailVerifiedAt     DateTime?  @db.Timestamptz(3)
  failedLoginAttempts Int        @default(0)
  lockedUntil         DateTime?  @db.Timestamptz(3)
  createdAt           DateTime   @default(now()) @db.Timestamptz(3)
  updatedAt           DateTime   @updatedAt @db.Timestamptz(3)

  credentials             UserCredential[]
  sessions                UserSession[]
  platformRoles           UserPlatformRole[]
  gameServers             GameServer[]
  auditEvents             AuditEvent[]
  emailVerificationTokens EmailVerificationToken[]
  passwordResetTokens     PasswordResetToken[]
  totpCredential          TotpCredential?
  creditWallet            CreditWallet?
  whmcsClientLink         WhmcsClientLink?
  ssoTickets              SsoTicket[]

  @@map("users")
}

model UserCredential {
  id           String   @id @default(uuid())
  userId       String
  passwordHash String
  createdAt    DateTime @default(now()) @db.Timestamptz(3)
  updatedAt    DateTime @updatedAt @db.Timestamptz(3)

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@map("user_credentials")
}

model UserSession {
  id        String    @id @default(uuid())
  userId    String
  tokenHash String    @unique
  expiresAt DateTime  @db.Timestamptz(3)
  revokedAt DateTime? @db.Timestamptz(3)
  ipAddress String?
  userAgent String?
  isImpersonation      Boolean @default(false)
  impersonationMetadata Json?
  createdAt DateTime  @default(now()) @db.Timestamptz(3)
  updatedAt DateTime  @updatedAt @db.Timestamptz(3)

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@index([expiresAt])
  @@map("user_sessions")
}

model EmailVerificationToken {
  id        String    @id @default(uuid())
  userId    String
  tokenHash String    @unique
  expiresAt DateTime  @db.Timestamptz(3)
  usedAt    DateTime? @db.Timestamptz(3)
  createdAt DateTime  @default(now()) @db.Timestamptz(3)

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@index([expiresAt])
  @@map("email_verification_tokens")
}

model PasswordResetToken {
  id        String    @id @default(uuid())
  userId    String
  tokenHash String    @unique
  expiresAt DateTime  @db.Timestamptz(3)
  usedAt    DateTime? @db.Timestamptz(3)
  createdAt DateTime  @default(now()) @db.Timestamptz(3)

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@index([expiresAt])
  @@map("password_reset_tokens")
}

model TotpCredential {
  id        String    @id @default(uuid())
  userId    String    @unique
  secret    String
  enabledAt DateTime? @db.Timestamptz(3)
  createdAt DateTime  @default(now()) @db.Timestamptz(3)
  updatedAt DateTime  @updatedAt @db.Timestamptz(3)

  user          User           @relation(fields: [userId], references: [id], onDelete: Cascade)
  recoveryCodes RecoveryCode[]

  @@map("totp_credentials")
}

model RecoveryCode {
  id               String    @id @default(uuid())
  totpCredentialId String
  codeHash         String
  usedAt           DateTime? @db.Timestamptz(3)
  createdAt        DateTime  @default(now()) @db.Timestamptz(3)

  totpCredential TotpCredential @relation(fields: [totpCredentialId], references: [id], onDelete: Cascade)

  @@index([totpCredentialId])
  @@map("recovery_codes")
}

model PlatformRole {
  id          String   @id @default(uuid())
  name        String   @unique
  description String?
  createdAt   DateTime @default(now()) @db.Timestamptz(3)
  updatedAt   DateTime @updatedAt @db.Timestamptz(3)

  userRoles UserPlatformRole[]

  @@map("platform_roles")
}

model UserPlatformRole {
  id        String   @id @default(uuid())
  userId    String
  roleId    String
  createdAt DateTime @default(now()) @db.Timestamptz(3)
  updatedAt DateTime @updatedAt @db.Timestamptz(3)

  user User         @relation(fields: [userId], references: [id], onDelete: Cascade)
  role PlatformRole @relation(fields: [roleId], references: [id], onDelete: Cascade)

  @@unique([userId, roleId])
  @@index([userId])
  @@index([roleId])
  @@map("user_platform_roles")
}

model GameServer {
  id               String           @id @default(uuid())
  userId           String
  planId           String?
  nodeId           String?
  name             String
  status           GameServerStatus @default(DRAFT)
  version          Int              @default(1)
  edition          ServerEdition    @default(JAVA)
  softwareFamily   SoftwareFamily   @default(VANILLA)
  minecraftVersion String           @default("1.21.1")
  eulaAccepted     Boolean          @default(false)
  hostPort         Int?
  containerId      String?
  dataPath         String?
  ramMb                Int              @default(1024)
  activeWorldName      String           @default("world")
  idleShutdownEnabled  Boolean          @default(true)
  lastPlayerSeenAt     DateTime?        @db.Timestamptz(3)
  idleStopAt           DateTime?        @db.Timestamptz(3)
  runningSince         DateTime?        @db.Timestamptz(3)
  lastMeteredAt        DateTime?        @db.Timestamptz(3)
  joinSlug             String?          @unique
  joinToStartEnabled   Boolean          @default(true)
  billingSuspendedAt   DateTime?        @db.Timestamptz(3)
  billingSuspendReason String?
  createdAt            DateTime         @default(now()) @db.Timestamptz(3)
  updatedAt            DateTime         @updatedAt @db.Timestamptz(3)

  user             User                        @relation(fields: [userId], references: [id])
  plan             Plan?                       @relation(fields: [planId], references: [id])
  node             GameNode?                   @relation(fields: [nodeId], references: [id])
  stateTransitions GameServerStateTransition[]
  allocation       GameServerAllocation?
  backups          ServerBackup[]
  backupRestores   BackupRestore[]
  backupSchedule   BackupSchedule?
  worldUploads     WorldUpload[]
  installedAddons  InstalledAddon[]
  startQueueEntry  ServerStartQueueEntry?
  usageRecords     UsageRecord[]
  dnsRecords       ServerDnsRecord[]
  whmcsServiceLink WhmcsServiceLink?
  ssoTickets       SsoTicket[]

  @@index([userId])
  @@index([nodeId])
  @@index([status])
  @@map("game_servers")
}

model GameServerAllocation {
  id            String           @id @default(uuid())
  serverId      String           @unique
  nodeId        String
  hostPort      Int
  ramMbReserved Int
  status        AllocationStatus @default(ACTIVE)
  createdAt     DateTime         @default(now()) @db.Timestamptz(3)
  updatedAt     DateTime         @updatedAt @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
  node   GameNode   @relation(fields: [nodeId], references: [id])

  @@unique([nodeId, hostPort])
  @@index([nodeId])
  @@map("game_server_allocations")
}

model GameServerStateTransition {
  id            String            @id @default(uuid())
  gameServerId  String
  fromStatus    GameServerStatus?
  toStatus      GameServerStatus
  reason        String?
  correlationId String?
  actorId       String?
  errorCode     String?
  errorMessage  String?
  metadata      Json?
  createdAt     DateTime          @default(now()) @db.Timestamptz(3)

  gameServer GameServer @relation(fields: [gameServerId], references: [id], onDelete: Cascade)

  @@index([gameServerId])
  @@index([createdAt])
  @@map("game_server_state_transitions")
}

model GameNode {
  id                  String         @id @default(uuid())
  name                String         @unique
  hostname            String
  status              GameNodeStatus @default(OFFLINE)
  maxServers          Int            @default(10)
  maxRamMb            Int            @default(16384)
  platformRamMb       Int            @default(2048)
  portRangeStart      Int            @default(25565)
  portRangeEnd        Int            @default(25664)
  activeServers       Int            @default(0)
  lastHeartbeatAt     DateTime?      @db.Timestamptz(3)
  agentVersion        String?
  enrollmentTokenHash String?
  enrolledAt          DateTime?      @db.Timestamptz(3)
  drainRequestedAt    DateTime?      @db.Timestamptz(3)
  metadata            Json?
  createdAt           DateTime       @default(now()) @db.Timestamptz(3)
  updatedAt           DateTime       @updatedAt @db.Timestamptz(3)

  gameServers       GameServer[]
  heartbeats        GameNodeHeartbeat[]
  allocations       GameServerAllocation[]
  startQueueEntries ServerStartQueueEntry[]

  @@map("game_nodes")
}

model GameNodeHeartbeat {
  id        String   @id @default(uuid())
  nodeId    String
  payload   Json?
  createdAt DateTime @default(now()) @db.Timestamptz(3)

  node GameNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)

  @@index([nodeId])
  @@index([createdAt])
  @@map("game_node_heartbeats")
}

model ServerStartQueueEntry {
  id            String           @id @default(uuid())
  serverId      String           @unique
  nodeId        String
  status        StartQueueStatus @default(QUEUED)
  priority      Int              @default(0)
  correlationId String?
  requestedAt   DateTime         @default(now()) @db.Timestamptz(3)
  expiresAt     DateTime?        @db.Timestamptz(3)
  createdAt     DateTime         @default(now()) @db.Timestamptz(3)
  updatedAt     DateTime         @updatedAt @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
  node   GameNode   @relation(fields: [nodeId], references: [id])

  @@index([status, priority, requestedAt])
  @@index([nodeId])
  @@map("server_start_queue")
}

model Plan {
  id                        String   @id @default(uuid())
  name                      String   @unique
  slug                      String   @unique
  description               String?
  isFree                    Boolean  @default(false)
  isActive                  Boolean  @default(true)
  maxRamMb                  Int
  maxCpuCores               Float
  maxStorageMb              Int
  maxServersPerUser         Int?
  idleTimeoutMinutes        Int      @default(15)
  idleGraceAfterStartMinutes Int     @default(3)
  idleCountdownSeconds      Int      @default(120)
  queuePriority             Int      @default(0)
  monthlyCreditGrant        Int      @default(0)
  metadata                  Json?
  createdAt                 DateTime @default(now()) @db.Timestamptz(3)
  updatedAt                 DateTime @updatedAt @db.Timestamptz(3)

  gameServers GameServer[]

  @@map("plans")
}

model FeatureFlag {
  id          String   @id @default(uuid())
  key         String   @unique
  description String?
  enabled     Boolean  @default(false)
  metadata    Json?
  createdAt   DateTime @default(now()) @db.Timestamptz(3)
  updatedAt   DateTime @updatedAt @db.Timestamptz(3)

  @@map("feature_flags")
}

model SystemSetting {
  id        String   @id @default(uuid())
  key       String   @unique
  value     Json
  createdAt DateTime @default(now()) @db.Timestamptz(3)
  updatedAt DateTime @updatedAt @db.Timestamptz(3)

  @@map("system_settings")
}

model AuditEvent {
  id         String   @id @default(uuid())
  userId     String?
  action     String
  entityType String?
  entityId   String?
  metadata   Json?
  ipAddress  String?
  createdAt  DateTime @default(now()) @db.Timestamptz(3)

  user User? @relation(fields: [userId], references: [id], onDelete: SetNull)

  @@index([userId])
  @@index([action])
  @@index([entityType, entityId])
  @@index([createdAt])
  @@map("audit_events")
}

model JobRecord {
  id          String    @id @default(uuid())
  queue       String
  jobName     String
  status      JobStatus @default(PENDING)
  payload     Json?
  result      Json?
  error       String?
  attempts    Int       @default(0)
  startedAt   DateTime? @db.Timestamptz(3)
  completedAt DateTime? @db.Timestamptz(3)
  createdAt   DateTime  @default(now()) @db.Timestamptz(3)
  updatedAt   DateTime  @updatedAt @db.Timestamptz(3)

  @@index([queue, status])
  @@index([createdAt])
  @@map("job_records")
}

model CreditWallet {
  id          String   @id @default(uuid())
  userId      String   @unique
  balance     Int      @default(0)
  periodStart DateTime @default(now()) @db.Timestamptz(3)
  createdAt   DateTime @default(now()) @db.Timestamptz(3)
  updatedAt   DateTime @updatedAt @db.Timestamptz(3)

  user         User                @relation(fields: [userId], references: [id], onDelete: Cascade)
  transactions CreditTransaction[]

  @@map("credit_wallets")
}

model CreditTransaction {
  id             String                @id @default(uuid())
  walletId       String
  amount         Int
  type           CreditTransactionType
  referenceType  String?
  referenceId    String?
  idempotencyKey String                @unique
  metadata       Json?
  createdAt      DateTime              @default(now()) @db.Timestamptz(3)

  wallet CreditWallet @relation(fields: [walletId], references: [id], onDelete: Cascade)

  @@index([walletId, createdAt])
  @@map("credit_transactions")
}

model UsageRecord {
  id             String   @id @default(uuid())
  serverId       String
  userId         String
  ramMb          Int
  durationSeconds Int
  creditsCharged Int
  periodStart    DateTime @db.Timestamptz(3)
  periodEnd      DateTime @db.Timestamptz(3)
  idempotencyKey String   @unique
  createdAt      DateTime @default(now()) @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@index([serverId, periodStart])
  @@index([userId, periodStart])
  @@map("usage_records")
}

model ServerBackup {
  id            String       @id @default(uuid())
  serverId      String
  type          BackupType   @default(MANUAL)
  status        BackupStatus @default(PENDING)
  s3Key         String?
  sizeBytes     BigInt?
  sha256        String?
  label         String?
  failureReason String?
  createdAt     DateTime     @default(now()) @db.Timestamptz(3)
  completedAt   DateTime?    @db.Timestamptz(3)
  expiresAt     DateTime?    @db.Timestamptz(3)

  server              GameServer      @relation(fields: [serverId], references: [id], onDelete: Cascade)
  restores            BackupRestore[] @relation("RestoredFromBackup")
  safetyForRestores   BackupRestore[] @relation("SafetyBackup")

  @@index([serverId])
  @@index([status])
  @@index([createdAt])
  @@map("server_backups")
}

model BackupRestore {
  id             String              @id @default(uuid())
  serverId       String
  backupId       String
  safetyBackupId String?
  status         BackupRestoreStatus @default(PENDING)
  failureReason  String?
  createdAt      DateTime            @default(now()) @db.Timestamptz(3)
  completedAt    DateTime?           @db.Timestamptz(3)

  server       GameServer    @relation(fields: [serverId], references: [id], onDelete: Cascade)
  backup       ServerBackup  @relation("RestoredFromBackup", fields: [backupId], references: [id])
  safetyBackup ServerBackup? @relation("SafetyBackup", fields: [safetyBackupId], references: [id])

  @@index([serverId])
  @@index([backupId])
  @@index([status])
  @@map("backup_restores")
}

model BackupSchedule {
  id             String    @id @default(uuid())
  serverId       String    @unique
  enabled        Boolean   @default(true)
  intervalHours  Int       @default(24)
  retentionCount Int       @default(5)
  nextRunAt      DateTime? @db.Timestamptz(3)
  lastRunAt      DateTime? @db.Timestamptz(3)
  createdAt      DateTime  @default(now()) @db.Timestamptz(3)
  updatedAt      DateTime  @updatedAt @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@map("backup_schedules")
}

model WorldUpload {
  id            String            @id @default(uuid())
  serverId      String
  status        WorldUploadStatus @default(PENDING)
  s3Key         String
  worldName     String            @default("world")
  sha256        String?
  sizeBytes     BigInt?
  failureReason String?
  createdAt     DateTime          @default(now()) @db.Timestamptz(3)
  completedAt   DateTime?         @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@index([serverId])
  @@index([status])
  @@map("world_uploads")
}

model InstalledAddon {
  id            String             @id @default(uuid())
  serverId      String
  provider      String             @default("modrinth")
  projectId     String
  projectSlug   String
  projectName   String
  versionId     String
  versionNumber String
  addonType     AddonType
  fileName      String
  filePath      String
  fileSha512    String?
  status        AddonInstallStatus @default(PENDING)
  failureReason String?
  installedAt   DateTime?          @db.Timestamptz(3)
  createdAt     DateTime           @default(now()) @db.Timestamptz(3)
  updatedAt     DateTime           @updatedAt @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@unique([serverId, projectId, versionId])
  @@index([serverId])
  @@index([status])
  @@map("installed_addons")
}

model ServerDnsRecord {
  id           String          @id @default(uuid())
  serverId     String
  fqdn         String
  recordType   String
  target       String
  port         Int?
  srvPriority  Int?
  srvWeight    Int?
  status       DnsRecordStatus @default(PENDING)
  lastSyncedAt DateTime?       @db.Timestamptz(3)
  lastError    String?
  createdAt    DateTime        @default(now()) @db.Timestamptz(3)
  updatedAt    DateTime        @updatedAt @db.Timestamptz(3)

  server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@unique([serverId, recordType, fqdn])
  @@index([fqdn])
  @@map("server_dns_records")
}

model WhmcsInstallation {
  id            String   @id @default(uuid())
  integrationId String   @unique
  name          String
  apiSecret     String
  mtlsClientFingerprint String?
  mtlsClientSubject     String?
  isActive      Boolean  @default(true)
  metadata      Json?
  createdAt     DateTime @default(now()) @db.Timestamptz(3)
  updatedAt     DateTime @updatedAt @db.Timestamptz(3)

  clientLinks  WhmcsClientLink[]
  serviceLinks WhmcsServiceLink[]
  operations   IntegrationOperation[]
  ssoTickets   SsoTicket[]
  events       IntegrationEvent[]
  syncCursor   IntegrationSyncCursor?
  reconciliationIssues ReconciliationIssue[]
  usageExports WhmcsUsageExport[]

  @@map("whmcs_installations")
}

model WhmcsClientLink {
  id               String   @id @default(uuid())
  installationId   String
  externalClientId String
  userId           String   @unique
  createdAt        DateTime @default(now()) @db.Timestamptz(3)
  updatedAt        DateTime @updatedAt @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)
  user         User              @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([installationId, externalClientId])
  @@map("whmcs_client_links")
}

model WhmcsServiceLink {
  id                String             @id @default(uuid())
  installationId    String
  externalServiceId String
  serverId          String             @unique
  externalProductId String?
  status            WhmcsServiceStatus @default(PENDING)
  suspendedAt       DateTime?          @db.Timestamptz(3)
  terminatedAt      DateTime?          @db.Timestamptz(3)
  lastReconciledAt  DateTime?          @db.Timestamptz(3)
  metadata          Json?
  createdAt         DateTime           @default(now()) @db.Timestamptz(3)
  updatedAt         DateTime           @updatedAt @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)
  server       GameServer          @relation(fields: [serverId], references: [id], onDelete: Cascade)

  @@unique([installationId, externalServiceId])
  @@index([installationId, status])
  @@map("whmcs_service_links")
}

model IntegrationOperation {
  id             String                     @id @default(uuid())
  installationId String
  idempotencyKey String                     @unique
  operation      String
  externalRef    String?
  status         IntegrationOperationStatus @default(COMPLETED)
  result         Json?
  error          String?
  createdAt      DateTime                   @default(now()) @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)

  @@index([installationId, createdAt])
  @@map("integration_operations")
}

model SsoTicket {
  id                String        @id @default(uuid())
  installationId    String
  tokenHash         String        @unique
  kind              SsoTicketKind
  userId            String
  serverId          String?
  externalServiceId String?
  externalClientId  String?
  externalUserId    String?
  targetPath        String
  issuer            String
  audience          String
  nonce             String        @unique
  expiresAt         DateTime      @db.Timestamptz(3)
  usedAt            DateTime?     @db.Timestamptz(3)
  createdAt         DateTime      @default(now()) @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)
  user         User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  server       GameServer?       @relation(fields: [serverId], references: [id], onDelete: SetNull)

  @@index([expiresAt])
  @@index([installationId, createdAt])
  @@map("sso_tickets")
}

model IntegrationEvent {
  id               String                 @id @default(uuid())
  installationId   String
  eventType        String
  payload          Json
  dedupeKey        String                 @unique
  status           IntegrationEventStatus @default(PENDING)
  acknowledgedAt   DateTime?              @db.Timestamptz(3)
  deadLetteredAt   DateTime?              @db.Timestamptz(3)
  deadLetterReason String?
  createdAt        DateTime               @default(now()) @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)

  @@index([installationId, status, createdAt])
  @@index([installationId, createdAt])
  @@map("integration_events")
}

model IntegrationSyncCursor {
  id             String   @id @default(uuid())
  installationId String   @unique
  eventsCursor   String?
  updatedAt      DateTime @updatedAt @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)

  @@map("integration_sync_cursors")
}

model ReconciliationIssue {
  id                String                 @id @default(uuid())
  installationId    String
  runId             String
  externalServiceId String?
  serverId          String?
  code              String
  severity          ReconciliationSeverity
  message           String
  details           Json?
  autoRepaired      Boolean                @default(false)
  resolvedAt        DateTime?              @db.Timestamptz(3)
  createdAt         DateTime               @default(now()) @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)

  @@index([installationId, runId])
  @@index([installationId, resolvedAt])
  @@map("reconciliation_issues")
}

model WhmcsUsageExport {
  id                String            @id @default(uuid())
  installationId    String
  externalServiceId String
  serverId          String
  periodStart       DateTime          @db.Timestamptz(3)
  periodEnd         DateTime          @db.Timestamptz(3)
  metrics           Json
  contentHash       String
  status            UsageExportStatus @default(DRAFT)
  isTestMode        Boolean           @default(false)
  adjustmentOfId    String?
  exportedAt        DateTime?         @db.Timestamptz(3)
  createdAt         DateTime          @default(now()) @db.Timestamptz(3)

  installation WhmcsInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade)

  @@unique([installationId, externalServiceId, periodStart, periodEnd, isTestMode])
  @@index([installationId, periodEnd])
  @@map("whmcs_usage_exports")
}
