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

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

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  name          String?
  passwordHash  String?
  image         String?
  emailVerified DateTime? // null = not verified, timestamp = verified
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt

  stripeCustomerId       String?   @unique
  stripeSubscriptionId   String?   @unique
  stripePriceId          String?
  stripeCurrentPeriodEnd DateTime?

  // Stripe Connect Express — operator payouts (Phase 5)
  stripeConnectAccountId         String?   @unique
  stripeConnectOnboardingComplete Boolean  @default(false)

  // Marketplace prepaid credits (Phase 5) — buyer balance in cents
  marketplaceCreditsCents Int @default(0)
  // Marketplace operator agreement acceptance (Phase 5, #304)
  marketplaceTermsAcceptedAt     DateTime?

  plan          String    @default("free")  // free, pro, team, enterprise

  teamId        String?  // null = not in a team
  team          Team?    @relation(fields: [teamId], references: [id])
  teamRole      String?  // admin, developer, viewer (null if not in team)

  costEntries   CostEntry[]
  sessions      Session[]
  accounts      Account[]
  apiKeys       ApiKey[]
  feedback      Feedback[]
  devices       Device[]
  deviceCodes   DeviceCode[]
  ownedTeams    Team[]         @relation("TeamOwner")
  teamInvites   TeamInvite[]   @relation("InvitedBy")
  tokenConversations     TokenConversation[]
  marketplaceUsageEvents MarketplaceUsageEvent[]
  events                 UserEvent[]
  operatorSettings       OperatorSettings?
  billingEventsAsOperator MarketplaceBillingEvent[] @relation("OperatorBilling")
  billingEventsAsBuyer    MarketplaceBillingEvent[] @relation("BuyerBilling")
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?

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

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model CostEntry {
  id        String   @id @default(cuid())
  userId    String
  provider  String   // "openai", "anthropic", "manual"
  amount    Float    // dollars
  period    String   // "2026-01", "2026-02", etc.
  category  String   @default("api")  // "api", "subscription", "other"
  note      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

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

  @@index([userId, period])
}

model ApiKey {
  id         String   @id @default(cuid())
  userId     String
  name       String   // user-provided label, e.g. "Production", "CI/CD"
  key        String   @unique // the actual API key (lh_sk_...)
  prefix     String   // first 8 chars for display (lh_sk_Ab...)
  proxyKeyId String?  @unique // proxy key id (from Python) for marketplace usage attribution
  lastUsed   DateTime?
  expiresAt  DateTime?
  createdAt  DateTime @default(now())
  revokedAt  DateTime?

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

  @@index([userId])
  @@index([key])
  @@index([proxyKeyId])
}

// Marketplace usage (Phase 5 buyer dashboard) — written by proxy via POST /api/internal/marketplace/usage
model MarketplaceUsageEvent {
  id               String   @id @default(cuid())
  userId           String
  requestId        String
  deviceId         String?   // remote node / marketplace device id
  model            String
  promptTokens     Int      @default(0)
  completionTokens Int      @default(0)
  priceCents       Int?     // optional charge for this request
  createdAt        DateTime @default(now())

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

  @@index([userId])
  @@index([createdAt])
}

model VerificationToken {
  identifier String   // email address
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

model Feedback {
  id          String   @id @default(cuid())
  userId      String
  requestId   String?  // optional link to a proxy request / audit log entry
  rating      Int?     // 1-5 star rating (nullable — user may only give thumbs)
  thumbsUp    Boolean  // true = thumbs up, false = thumbs down
  comment     String?  // optional free-text comment
  model       String?  // model name that produced the response
  createdAt   DateTime @default(now())

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

  @@index([userId])
  @@index([requestId])
}

model PasswordResetToken {
  id         String   @id @default(cuid())
  email      String
  token      String   @unique
  expires    DateTime
  createdAt  DateTime @default(now())

  @@unique([email, token])
  @@index([token])
  @@index([email])
}

model Device {
  id          String   @id @default(cuid())
  userId      String
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  name        String
  tunnelUrl   String
  models      String   @default("[]") // JSON array of model names
  hardware    String   @default("")   // JSON hardware summary
  status      String   @default("online") // online, offline, degraded
  lastSeen    DateTime @default(now())
  latencyMs   Int?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  // Enhanced heartbeat fields (Phase 3 dashboard)
  vramUsageMb     Int?      // Current VRAM usage in megabytes
  activeModel     String?   // Currently loaded/active model name
  requestsServed  Int       @default(0) // Total requests served this session
  uptimeSeconds   Int       @default(0) // Seconds since device came online
  savingsUsd      Float     @default(0) // Cumulative savings in USD this session

  // Phase 5 marketplace: operator opt-in
  marketplaceEnabled    Boolean @default(false)
  marketplacePriceCents Int?    // price per GPU-hour in cents (e.g. 15 = $0.15/hr)

  billingEvents MarketplaceBillingEvent[]

  @@unique([userId, name])
  @@index([userId])
  @@index([status])
  @@index([marketplaceEnabled])
}

model DeviceCode {
  id               String   @id @default(cuid())
  deviceCode       String   @unique // opaque code for polling
  userCode         String   @unique // human-readable code (e.g. ABCD-1234)
  userId           String?          // null until user approves
  user             User?    @relation(fields: [userId], references: [id], onDelete: Cascade)
  approved         Boolean  @default(false)
  apiKeyId         String?          // set on approval — the API key granted
  expires          DateTime
  createdAt        DateTime @default(now())

  @@index([deviceCode])
  @@index([userCode])
}

model Team {
  id                     String   @id @default(cuid())
  name                   String
  ownerId                String
  owner                  User     @relation("TeamOwner", fields: [ownerId], references: [id])
  stripeSubscriptionId   String?  @unique
  maxSeats               Int      @default(15)
  monthlyBudget          Float?   // USD spending cap for the whole team
  createdAt              DateTime @default(now())
  updatedAt              DateTime @updatedAt

  members                User[]
  invites                TeamInvite[]

  @@index([ownerId])
}

model TeamInvite {
  id         String   @id @default(cuid())
  teamId     String
  team       Team     @relation(fields: [teamId], references: [id], onDelete: Cascade)
  email      String   // invitee email
  role       String   @default("developer") // admin, developer, viewer
  token      String   @unique // secure invite token
  invitedById String
  invitedBy  User     @relation("InvitedBy", fields: [invitedById], references: [id])
  expiresAt  DateTime
  acceptedAt DateTime?
  createdAt  DateTime @default(now())

  @@unique([teamId, email])
  @@index([token])
  @@index([email])
}

model MaintenanceWindow {
  id        String   @id @default(cuid())
  deviceId  String
  userId    String
  startTime DateTime
  endTime   DateTime
  reason    String   @default("")
  recurring String   @default("none") // none, daily, weekly
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([deviceId])
  @@index([userId])
  @@index([startTime, endTime])
}

// ---------------------------------------------------------------------------
// Hardware Performance Atlas — crowdsourced GPU × model performance data
// ---------------------------------------------------------------------------

model AtlasEntry {
  id                  String   @id @default(cuid())
  gpuModel            String
  vramMb              Int
  modelId             String
  quantization        String   @default("unknown")
  tpsP50              Float
  tpsP10              Float
  successRate         Float
  measurementCount    Int
  firstTokenLatencyMs Float
  updatedAt           DateTime @updatedAt

  @@unique([gpuModel, vramMb, modelId, quantization])
}

// ---------------------------------------------------------------------------
// Token — Conversational Fleet Orchestrator
// ---------------------------------------------------------------------------

model TokenConfig {
  id                String   @id @default("singleton") // single-row config
  enabled           Boolean  @default(true)
  chatModel         String   @default("google/gemini-2.5-flash")
  researchModel     String   @default("anthropic/claude-sonnet-4.6")
  reasoningModel    String   @default("deepseek/deepseek-r1")
  classificationModel String @default("google/gemini-2.5-flash-lite")
  systemPrompt      String   @default("")
  maxTokensPerReply Int      @default(4096)
  temperature       Float    @default(0.4)
  monthlyBudgetUsd  Float    @default(50.0) // cost cap
  totalSpentUsd     Float    @default(0.0)
  updatedAt         DateTime @updatedAt
}

model TokenConversation {
  id        String   @id @default(cuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  title     String   @default("New conversation")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  messages  TokenMessage[]
  actions   TokenFleetAction[]

  @@index([userId])
}

model TokenMessage {
  id             String   @id @default(cuid())
  conversationId String
  conversation   TokenConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
  role           String   // "user" | "assistant" | "system"
  content        String
  model          String?  // which model answered (null for user messages)
  inputTokens    Int      @default(0)
  outputTokens   Int      @default(0)
  costUsd        Float    @default(0.0)
  latencyMs      Int      @default(0)
  messageType    String   @default("chat") // chat | research | fleet_action | deployment_plan
  createdAt      DateTime @default(now())

  @@index([conversationId])
}

// ---------------------------------------------------------------------------
// User Events — onboarding, FAE, lifecycle tracking
// ---------------------------------------------------------------------------

model UserEvent {
  id        String   @id @default(cuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  event     String   // "fae_api_call", "key_created", "subscription_started", "plan_upgraded", "plan_downgraded"
  metadata  String   @default("{}") // JSON blob for event-specific data
  createdAt DateTime @default(now())

  @@index([userId])
  @@index([event])
  @@index([userId, event])
}

model TokenFleetAction {
  id             String   @id @default(cuid())
  conversationId String
  conversation   TokenConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
  messageId      String?  // which assistant message proposed this action
  deviceId       String
  type           String   // pull_model | remove_model | restart_engine
  payload        String   @default("{}") // JSON: { model: "qwen3:8b", ... }
  status         String   @default("pending") // pending | approved | in_progress | success | failed
  result         String?  // JSON result on completion
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt

  @@index([conversationId])
  @@index([deviceId])
  @@index([status])
}

// ---------------------------------------------------------------------------
// Marketplace Operator — settings and earnings tracking
// ---------------------------------------------------------------------------

model OperatorSettings {
  id                 String   @id @default(cuid())
  userId             String   @unique
  user               User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  marketplaceEnabled Boolean  @default(false)
  pricePerMToken     Float    @default(0.10)  // price per million tokens
  minPrice           Float    @default(0.01)
  maxPrice           Float    @default(1.00)
  autoPrice          Boolean  @default(true)  // dynamic pricing based on demand
  availableModels    String   @default("[]")  // JSON array of model names
  createdAt          DateTime @default(now())
  updatedAt          DateTime @updatedAt
}

model MarketplaceEarning {
  id            String   @id @default(cuid())
  operatorId    String
  deviceId      String
  model         String
  tokensIn      Int
  tokensOut     Int
  revenue       Float    // what the consumer paid
  operatorShare Float    // what the operator earns (after platform fee)
  consumerId    String   // who used the compute
  createdAt     DateTime @default(now())

  @@index([operatorId])
  @@index([createdAt])
}

// ---------------------------------------------------------------------------
// Marketplace Billing Events — per-request billing for marketplace usage
// Records every billable request flowing through the marketplace.
// Used for operator payout calculations, buyer spending, and platform revenue.
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Site Configuration — singleton for admin-controlled settings
// ---------------------------------------------------------------------------

model SiteConfig {
  id          String   @id @default("singleton")
  landingMode String   @default("open") // "open" or "exclusive"
  updatedAt   DateTime @updatedAt
}

model WaitlistEntry {
  id        String   @id @default(cuid())
  email     String   @unique
  createdAt DateTime @default(now())
}

model MarketplaceBillingEvent {
  id                 String   @id @default(cuid())
  deviceId           String
  operatorId         String
  buyerId            String
  model              String
  promptTokens       Int
  completionTokens   Int
  totalTokens        Int
  priceCents         Int      // total price for this request in cents
  platformFeeCents   Int      // 20% platform fee
  operatorShareCents Int      // 80% operator share
  paidOut            Boolean  @default(false) // true after Transfer to operator
  payoutTransferId   String?  // Stripe Transfer ID when paid out
  createdAt          DateTime @default(now())

  device   Device @relation(fields: [deviceId], references: [id])
  operator User   @relation("OperatorBilling", fields: [operatorId], references: [id])
  buyer    User   @relation("BuyerBilling", fields: [buyerId], references: [id])

  @@index([operatorId])
  @@index([buyerId])
  @@index([deviceId])
  @@index([createdAt])
  @@index([operatorId, paidOut])
}
