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
  STARTING
  RUNNING
  STOPPING
  ERROR
  DELETING
  DELETED
}

enum GameNodeStatus {
  ONLINE
  OFFLINE
  MAINTENANCE
  DRAINING
  UNREACHABLE
}

enum AllocationStatus {
  ACTIVE
  RELEASED
}

enum JobStatus {
  PENDING
  RUNNING
  COMPLETED
  FAILED
  CANCELLED
}

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?

  @@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?
  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)
  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?

  @@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)
  activeServers       Int            @default(0)
  lastHeartbeatAt     DateTime?      @db.Timestamptz(3)
  agentVersion        String?
  enrollmentTokenHash String?
  enrolledAt          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[]

  @@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 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
  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")
}
