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

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

// ─── Auth & Billing ────────────────────────────────────────────

model User {
  id            String   @id @default(cuid())
  email         String   @unique
  passwordHash  String
  name          String?
  emailVerified Boolean  @default(false)

  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt

  subscription  Subscription?
  apiKeys       ApiKey[]
  traces        Trace[]
  passwordResetTokens PasswordResetToken[]
  emailVerificationTokens EmailVerificationToken[]

  @@index([email])
}

model PasswordResetToken {
  id        String   @id @default(cuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  token     String   @unique   // SHA-256 hash of the raw token
  expiresAt DateTime
  used      Boolean  @default(false)

  createdAt DateTime @default(now())

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

model EmailVerificationToken {
  id        String   @id @default(cuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  token     String   @unique   // SHA-256 hash of the raw token
  expiresAt DateTime
  used      Boolean  @default(false)

  createdAt DateTime @default(now())

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

model ApiKey {
  id          String   @id @default(cuid())
  userId      String
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  name        String   @default("Default")
  keyHash     String   @unique          // SHA-256 hash of the actual key
  keyPrefix   String                     // First 8 chars for display: "al_xxxx..."
  lastUsedAt  DateTime?

  revoked     Boolean  @default(false)
  createdAt   DateTime @default(now())

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

model Subscription {
  id              String           @id @default(cuid())
  userId          String           @unique
  user            User             @relation(fields: [userId], references: [id], onDelete: Cascade)

  tier            SubscriptionTier @default(FREE)
  stripeCustomerId    String?      @unique
  stripeSubscriptionId String?     @unique
  stripePriceId       String?

  currentPeriodStart  DateTime?
  currentPeriodEnd    DateTime?

  // Usage tracking for the current billing period
  sessionsUsed    Int              @default(0)
  sessionsLimit   Int              @default(20)   // Free tier: 20/day, paid: per month

  status          SubscriptionStatus @default(ACTIVE)

  createdAt       DateTime         @default(now())
  updatedAt       DateTime         @updatedAt

  @@index([stripeCustomerId])
  @@index([stripeSubscriptionId])
}

enum SubscriptionTier {
  FREE        // 20 sessions/day
  STARTER     // $5/mo — 1,000 sessions/mo
  PRO         // $20/mo — 100,000 sessions/mo
}

enum SubscriptionStatus {
  ACTIVE
  PAST_DUE
  CANCELED
  UNPAID
}

// ─── Observability ─────────────────────────────────────────────

model Trace {
  id          String   @id @default(cuid())
  sessionId   String?
  name        String
  status      TraceStatus @default(RUNNING)
  tags        String[] @default([])
  metadata    Json?

  // Owner — nullable for backward compat with existing unowned traces
  userId      String?
  user        User?    @relation(fields: [userId], references: [id], onDelete: SetNull)

  totalCost     Float?
  totalTokens   Int?
  totalDuration Int?

  startedAt   DateTime @default(now())
  endedAt     DateTime?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  decisionPoints DecisionPoint[]
  spans          Span[]
  events         Event[]

  @@index([sessionId])
  @@index([status])
  @@index([createdAt])
  @@index([name])
  @@index([userId])
}

model DecisionPoint {
  id          String   @id @default(cuid())
  traceId     String
  trace       Trace    @relation(fields: [traceId], references: [id], onDelete: Cascade)

  type        DecisionType
  reasoning   String?

  chosen      Json
  alternatives Json[]

  contextSnapshot Json?

  durationMs  Int?
  costUsd     Float?

  parentSpanId String?
  span        Span?    @relation(fields: [parentSpanId], references: [id])

  timestamp   DateTime @default(now())

  @@index([traceId])
  @@index([type])
  @@index([timestamp])
}

model Span {
  id          String   @id @default(cuid())
  traceId     String
  trace       Trace    @relation(fields: [traceId], references: [id], onDelete: Cascade)

  parentSpanId String?
  parentSpan   Span?   @relation("SpanTree", fields: [parentSpanId], references: [id])
  childSpans   Span[]  @relation("SpanTree")

  name        String
  type        SpanType

  input       Json?
  output      Json?

  tokenCount  Int?
  costUsd     Float?
  durationMs  Int?

  status      SpanStatus @default(RUNNING)
  statusMessage String?

  startedAt   DateTime @default(now())
  endedAt     DateTime?

  metadata    Json?

  decisionPoints DecisionPoint[]

  @@index([traceId])
  @@index([parentSpanId])
  @@index([type])
  @@index([startedAt])
}

model Event {
  id          String   @id @default(cuid())
  traceId     String
  trace       Trace    @relation(fields: [traceId], references: [id], onDelete: Cascade)

  spanId      String?
  type        EventType
  name        String

  metadata    Json?

  timestamp   DateTime @default(now())

  @@index([traceId])
  @@index([type])
  @@index([timestamp])
}

enum TraceStatus {
  RUNNING
  COMPLETED
  ERROR
}

enum DecisionType {
  TOOL_SELECTION
  ROUTING
  RETRY
  ESCALATION
  MEMORY_RETRIEVAL
  PLANNING
  CUSTOM
}

enum SpanType {
  LLM_CALL
  TOOL_CALL
  MEMORY_OP
  CHAIN
  AGENT
  CUSTOM
}

enum SpanStatus {
  RUNNING
  COMPLETED
  ERROR
}

enum EventType {
  ERROR
  RETRY
  FALLBACK
  CONTEXT_OVERFLOW
  USER_FEEDBACK
  CUSTOM
}
