generator client {
  provider = "prisma-client-js"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

enum UserRole {
  ADMIN
  USER
  OPERATOR
  VIEWER
}

enum UserStatus {
  ACTIVE
  SUSPENDED
  PENDING
}

enum UserLanguagePreference {
  EN
  AR
  FR
  IT
  DE
  ES
  RU
  PT
}

enum UserAppearancePreference {
  SYSTEM
  LIGHT
  DARK
}

model User {
  id                 String     @id @default(cuid())
  email              String     @unique
  username           String     @unique
  passwordHash       String     @map("password_hash")
  role               UserRole   @default(USER)
  // Dynamic RBAC FK (Auth/RBAC/Plans flagship). Nullable during the backfill
  // window; the resolver falls back to the `role` enum when roleId is null.
  roleId             String?    @map("role_id")
  // Fast pointer to the user's active plan (Phase B). Nullable: ADMIN bypasses
  // plans entirely, and the column is backfilled for existing users.
  activePlanId       String?    @map("active_plan_id")
  status             UserStatus @default(PENDING)
  mustChangePassword    Boolean                  @default(false) @map("must_change_password")
  languagePreference    UserLanguagePreference   @default(EN) @map("language_preference")
  appearancePreference  UserAppearancePreference @default(SYSTEM) @map("appearance_preference")
  createdAt             DateTime                 @default(now()) @map("created_at")
  updatedAt             DateTime                 @updatedAt @map("updated_at")

  sessions        Session[]
  roleRef         Role?               @relation(fields: [roleId], references: [id])
  activePlan      Plan?               @relation("UserActivePlan", fields: [activePlanId], references: [id])
  planAssignments UserPlanAssignment[]

  @@index([email])
  @@index([status])
  @@index([roleId])
  @@index([activePlanId])
  @@map("users")
}

enum PlanAssignmentStatus {
  ACTIVE
  CANCELLED
  EXPIRED
  PAUSED
}

// Admin-managed subscription plan (Phase B). Prices are modeled so billing can
// be added later; no payment integration ships now.
model Plan {
  id                     String   @id @default(cuid())
  name                   String
  slug                   String   @unique
  description            String?
  priceMonthly           Decimal? @map("price_monthly") @db.Decimal(12, 2)
  priceYearly            Decimal? @map("price_yearly") @db.Decimal(12, 2)
  currency               String?  @default("USD")
  displayOrder           Int      @default(0) @map("display_order")
  isDefault              Boolean  @default(false) @map("is_default")
  isActive               Boolean  @default(true) @map("is_active")
  isPublic               Boolean  @default(true) @map("is_public")
  dailyTokenQuota        Int      @default(0) @map("daily_token_quota")
  monthlyTokenQuota      Int?     @map("monthly_token_quota")
  maxChatsPerDay         Int?     @map("max_chats_per_day")
  maxMessagesPerDay      Int?     @map("max_messages_per_day")
  maxWorkspaceConnections Int?    @map("max_workspace_connections")
  maxContextPacks        Int?     @map("max_context_packs")
  maxMemoryItems         Int?     @map("max_memory_items")
  allowCompareMode       Boolean  @default(false) @map("allow_compare_mode")
  allowJudgeMode         Boolean  @default(false) @map("allow_judge_mode")
  allowResearchMode      Boolean  @default(false) @map("allow_research_mode")
  allowCriticReview      Boolean  @default(false) @map("allow_critic_review")
  allowWorkspaces        Boolean  @default(true) @map("allow_workspaces")
  allowMemory            Boolean  @default(true) @map("allow_memory")
  allowContextPacks      Boolean  @default(true) @map("allow_context_packs")
  createdAt              DateTime @default(now()) @map("created_at")
  updatedAt              DateTime @updatedAt @map("updated_at")

  modelAccess     PlanModelAccess[]
  assignments     UserPlanAssignment[]
  activeForUsers  User[]               @relation("UserActivePlan")

  @@index([isActive])
  @@index([displayOrder])
  @@map("plans")
}

// Per-plan provider/model access. Backend source of truth for what a plan's
// users may call, and in which role (primary/fallback/judge/compare).
model PlanModelAccess {
  id                    String   @id @default(cuid())
  planId                String   @map("plan_id")
  provider              String
  model                 String
  isAllowed             Boolean  @default(true) @map("is_allowed")
  allowAsPrimary        Boolean  @default(true) @map("allow_as_primary")
  allowAsFallback       Boolean  @default(true) @map("allow_as_fallback")
  allowAsJudge          Boolean  @default(false) @map("allow_as_judge")
  allowInCompare        Boolean  @default(true) @map("allow_in_compare")
  dailyTokenLimitOverride Int?   @map("daily_token_limit_override")
  createdAt             DateTime @default(now()) @map("created_at")
  updatedAt             DateTime @updatedAt @map("updated_at")

  plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)

  @@unique([planId, provider, model])
  @@index([planId])
  @@map("plan_model_access")
}

// Historical record of plan assignments. The current ACTIVE row mirrors
// User.activePlanId; older rows are retained for audit.
model UserPlanAssignment {
  id               String               @id @default(cuid())
  userId           String               @map("user_id")
  planId           String               @map("plan_id")
  status           PlanAssignmentStatus @default(ACTIVE)
  assignedByUserId String?              @map("assigned_by_user_id")
  startsAt         DateTime             @default(now()) @map("starts_at")
  endsAt           DateTime?            @map("ends_at")
  createdAt        DateTime             @default(now()) @map("created_at")
  updatedAt        DateTime             @updatedAt @map("updated_at")

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

  @@index([userId])
  @@index([planId])
  @@index([status])
  @@map("user_plan_assignments")
}

// Durable per-user/day token ledger (source of truth + admin reporting). The
// runtime hot-path counter lives in Redis; this row is the reconciled total.
model TokenUsageLedger {
  id           String   @id @default(cuid())
  userId       String   @map("user_id")
  planId       String?  @map("plan_id")
  date         String
  inputTokens  Int      @default(0) @map("input_tokens")
  outputTokens Int      @default(0) @map("output_tokens")
  totalTokens  Int      @default(0) @map("total_tokens")
  requestCount Int      @default(0) @map("request_count")
  modelBreakdown Json?  @map("model_breakdown")
  createdAt    DateTime @default(now()) @map("created_at")
  updatedAt    DateTime @updatedAt @map("updated_at")

  @@unique([userId, date])
  @@index([userId])
  @@index([date])
  @@map("token_usage_ledger")
}

// Dynamic, admin-editable role (Auth/RBAC/Plans flagship). System roles
// (ADMIN, USER) are seeded with isSystem=true and cannot be deleted/renamed.
// Admins may create custom roles (e.g. SUPPORT, READ_ONLY_ADMIN) and grant
// any subset of the fixed Permission catalog via RolePermission rows.
model Role {
  id           String           @id @default(cuid())
  slug         String           @unique
  name         String
  description  String?
  isSystem     Boolean          @default(false) @map("is_system")
  isAssignable Boolean          @default(true) @map("is_assignable")
  createdAt    DateTime         @default(now()) @map("created_at")
  updatedAt    DateTime         @updatedAt @map("updated_at")

  permissions RolePermission[]
  users       User[]

  @@map("roles")
}

// One row per (role, permission) grant. `permission` stores a value from the
// fixed Permission code-catalog (shared-types). Admins toggle these rows.
model RolePermission {
  id         String   @id @default(cuid())
  roleId     String   @map("role_id")
  permission String
  createdAt  DateTime @default(now()) @map("created_at")

  role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)

  @@unique([roleId, permission])
  @@index([roleId])
  @@map("role_permissions")
}

model Session {
  id           String   @id @default(cuid())
  userId       String   @map("user_id")
  refreshToken String   @unique @map("refresh_token")
  expiresAt    DateTime @map("expires_at")
  createdAt    DateTime @default(now()) @map("created_at")

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

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

model SystemSetting {
  id        String   @id @default(cuid())
  key       String   @unique
  value     String
  updatedAt DateTime @updatedAt @map("updated_at")

  @@index([key])
  @@map("system_settings")
}
