generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "linux-musl-openssl-3.0.x", "debian-openssl-1.1.x", "debian-openssl-3.0.x"]
}

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

enum UserOfferType {
  NEW_USER_UNLOCK
}

enum UserOfferState {
  ACTIVE
  EXPIRED
  CONSUMED
  INELIGIBLE
}

enum UserPaymentGatewayPreference {
  AUTO
  STRIPE
  TELEGRAM_STARS
  NOWPAYMENTS
}

enum BlockReason {
  ABUSE_PROMPT    // Prompt 滥用检测
  ABUSE_IMAGE     // 图片滥用检测  
  FRAUD_EFW       // Stripe 早期欺诈预警
  USER_REQUESTED  // 用户主动删除
  ADMIN_MANUAL    // 管理员手动封禁
}

enum AffiliateEarningSourceType {
  ORDER_PAYMENT
  SUBSCRIPTION_RENEWAL
}

enum AffiliateEarningState {
  PENDING
  AVAILABLE
  VOIDED
}

enum AffiliateCurrency {
  USD
}

enum AffiliateLedgerKind {
  EARNING_CREATED
  EARNING_MATURED
  PAYOUT_REQUESTED
  PAYOUT_RELEASED
  PAYOUT_COMPLETED
  EXCHANGE_CREDITS
  EARNING_VOIDED
  EARNING_RESTORED
  ADJUSTMENT
}

enum AffiliateLedgerReferenceType {
  EARNING
  PAYOUT_REQUEST
  PAYMENT
  INVOICE
  ADMIN
}

enum AffiliatePayoutType {
  CASHOUT_USDT
  EXCHANGE_CREDITS
}

enum AffiliatePayoutStatus {
  REQUESTED
  APPROVED
  REJECTED
  COMPLETED
  FAILED
}

model Account {
  id                       String  @id @default(cuid())
  userId                   String  @map("user_id")
  type                     String
  provider                 String
  providerAccountId        String  @map("provider_account_id")
  refresh_token            String?
  access_token             String?
  expires_at               Int?
  token_type               String?
  scope                    String?
  id_token                 String?
  session_state            String?
  refresh_token_expires_in Int?
  user                     User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
  @@map("accounts")
}

model Conversation {
  id                  String        @id @default(cuid())
  userId              String?       @map("user_id")
  projectId           String?       @map("project_id")
  isGuest             Boolean       @default(false) @map("is_guest")
  isArchived          Boolean       @default(false) @map("is_archived")
  isShared            Boolean       @default(false) @map("is_shared")
  sharedAt            DateTime?     @map("shared_at")
  title               String?
  metadata            Json?
  activeInteractionId String?       @map("active_interaction_id")
  createdAt           DateTime      @default(now()) @map("created_at")
  updatedAt           DateTime      @updatedAt @map("updated_at")
  project             Project?        @relation(fields: [projectId], references: [id])
  user                User?           @relation(fields: [userId], references: [id], onDelete: Cascade)
  interactions        Interaction[]
  supportTickets      SupportTicket[]

  @@index([updatedAt])
  @@index([userId, createdAt])
  @@index([isGuest, createdAt])
  @@index([userId, isArchived])
  @@index([isShared])
  @@index([projectId])
  @@index([userId, projectId])
  @@map("conversations")
}

model Interaction {
  id             String        @id @default(cuid())
  conversationId String        @map("conversation_id")
  userAgentId    String?       @map("user_agent_id")
  type           String
  parts          Json          @default("[]")
  metadata       Json?
  correlationId  String?       @map("correlation_id")
  parentId       String?       @map("parent_id")
  createdAt      DateTime      @default(now()) @map("created_at")
  conversation   Conversation  @relation(fields: [conversationId], references: [id], onDelete: Cascade)
  parent         Interaction?  @relation("InteractionTree", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
  children       Interaction[] @relation("InteractionTree")
  userAgent      UserAgent?    @relation(fields: [userAgentId], references: [id], onDelete: Cascade)

  @@index([conversationId, createdAt])
  @@index([correlationId])
  @@index([userAgentId])
  @@index([parentId])
  @@map("interactions")
}

model Post {
  id          Int      @id @default(autoincrement())
  name        String
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")
  createdById String   @map("created_by_id")
  createdBy   User     @relation(fields: [createdById], references: [id])

  @@index([name])
  @@map("posts")
}

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

  @@map("sessions")
}

model User {
  id                       String                       @id @default(cuid())
  name                     String?
  email                    String?                      @unique
  emailVerified            DateTime?                    @map("email_verified")
  image                    String?
  timezone                 String?                      @default("UTC")
  countryCode              String?                      @map("country_code") // ISO 3166-1 alpha-2, e.g. "US", "DE"
  countryCodeSource        String?                      @map("country_code_source") // "AUTO" | "IP"
  countryCodeUpdatedAt     DateTime?                    @map("country_code_updated_at")
  isAdmin                  Boolean                      @default(false) @map("is_admin")
  utmCampaign              String?                      @map("utm_campaign")
  utmContent               String?                      @map("utm_content")
  utmMedium                String?                      @map("utm_medium")
  utmSource                String?                      @map("utm_source")
  utmTerm                  String?                      @map("utm_term")
  emailBounced             Boolean                      @default(false) @map("email_bounced")
  emailComplained          Boolean                      @default(false) @map("email_complained")
  createdAt                DateTime                     @default(now()) @map("created_at")
  isBlocked                Boolean                      @default(false) @map("is_blocked")
  blockedReason            BlockReason?                 @map("blocked_reason")
  blockedAt                DateTime?                    @map("blocked_at")
  canonicalEmail           String?                      @unique @map("canonical_email")
  hasPurchased             Boolean                      @default(false) @map("has_purchased")
  deviceKeyAtSignup        String?                      @map("device_key_at_signup")
  isPrimaryDeviceAccount   Boolean                      @default(true) @map("is_primary_device_account")
  stripeCustomerId         String?                      @map("stripe_customer_id")
  telegramId               String?                      @unique @map("telegram_id") // Telegram user ID for Stars payment
  paymentGatewayPreference UserPaymentGatewayPreference @default(AUTO) @map("payment_gateway_preference")
  signupIp                 String?                      @map("signup_ip")
  passwordHash             String?                      @map("password_hash")

  // Affiliate / Referral
  referralCode       String?   @unique @map("referral_code")
  referredById       String?   @map("referred_by_id")
  payoutWallet       String?   @map("payout_wallet")
  affiliateEnabledAt DateTime? @map("affiliate_enabled_at")

  referredBy User?  @relation("UserReferral", fields: [referredById], references: [id], onDelete: SetNull)
  referrals  User[] @relation("UserReferral")

  affiliateEarnings       AffiliateEarning[]       @relation("AffiliateUser")
  referredEarnings        AffiliateEarning[]       @relation("ReferredUser")
  affiliatePayoutRequests AffiliatePayoutRequest[]
  affiliateAccount        AffiliateAccount?
  affiliateLedgerEntries  AffiliateLedgerEntry[]

  // Ad Tracking
  adClickId       String?   @map("ad_click_id") // Stores gclid, gbraid, wbraid
  adClickProvider String?   @map("ad_click_provider") // e.g., "google"
  adClickTime     DateTime? @map("ad_click_time") // When the click happened

  accounts             Account[]
  activationCodes      ActivationCode[]
  conversations        Conversation[]
  documents            Document[]
  githubConnections    GitHubConnection?
  githubRepositories   GitHubRepository[]
  imageAssets          ImageAsset[]
  orders               Order[]               @relation("UserOrders")
  payments             Payment[]             @relation("UserPayments")
  posts                Post[]
  projects             Project[]
  sessions             Session[]
  userAgents           UserAgent[]
  stats                UserStats?
  offers               UserOffer[]
  attribution          UserAttribution?
  touchSchedules            TouchSchedule[]
  supportTickets            SupportTicket[]
  notificationPreferences   UserNotificationPreference[]
  @@index([createdAt])
  @@index([deviceKeyAtSignup, createdAt])
  @@index([utmSource])
  @@index([isBlocked])
  @@index([isPrimaryDeviceAccount])
  @@index([hasPurchased])
  @@index([signupIp])
  @@index([referredById])
  @@index([referralCode])
  @@map("users")
}

model AffiliateAccount {
  id             String            @id @default(uuid())
  userId         String            @unique @map("user_id")
  currency       AffiliateCurrency @default(USD)
  pendingCents   Int               @default(0) @map("pending_cents")
  availableCents Int               @default(0) @map("available_cents")
  lockedCents    Int               @default(0) @map("locked_cents")
  debtCents      Int               @default(0) @map("debt_cents")
  version        Int               @default(0)
  createdAt      DateTime          @default(now()) @map("created_at")
  updatedAt      DateTime          @updatedAt @map("updated_at")

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

  @@index([updatedAt])
  @@map("affiliate_accounts")
}

model AffiliateLedgerEntry {
  id             String                       @id @default(uuid())
  accountId      String                       @map("account_id")
  userId         String                       @map("user_id")
  currency       AffiliateCurrency            @default(USD)
  kind           AffiliateLedgerKind
  referenceType  AffiliateLedgerReferenceType @map("reference_type")
  referenceId    String?                      @map("reference_id")
  idempotencyKey String                       @unique @map("idempotency_key")

  deltaPendingCents   Int @default(0) @map("delta_pending_cents")
  deltaAvailableCents Int @default(0) @map("delta_available_cents")
  deltaLockedCents    Int @default(0) @map("delta_locked_cents")
  deltaDebtCents      Int @default(0) @map("delta_debt_cents")

  meta      Json?    @map("meta")
  createdAt DateTime @default(now()) @map("created_at")

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

  @@index([accountId, createdAt])
  @@index([userId, createdAt])
  @@index([referenceType, referenceId])
  @@map("affiliate_ledger_entries")
}

model AffiliateEarning {
  id               String                     @id @default(uuid())
  sourceType       AffiliateEarningSourceType @map("source_type")
  sourceExternalId String                     @map("source_external_id")
  sourceSequence   Int                        @default(0) @map("source_sequence")

  affiliateUserId String @map("affiliate_user_id")
  referredUserId  String @map("referred_user_id")

  orderId        String? @map("order_id")
  paymentId      String? @map("payment_id")
  subscriptionId String? @map("subscription_id")
  paymentGateway String? @map("payment_gateway")

  currency          AffiliateCurrency @default(USD)
  grossAmountCents  Int               @map("gross_amount_cents")
  commissionRateBps Int               @default(3000) @map("commission_rate_bps")
  commissionCents   Int               @map("commission_cents")

  state       AffiliateEarningState @default(PENDING)
  availableAt DateTime              @map("available_at")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  affiliateUser User @relation("AffiliateUser", fields: [affiliateUserId], references: [id], onDelete: Cascade)
  referredUser  User @relation("ReferredUser", fields: [referredUserId], references: [id], onDelete: Cascade)

  @@unique([sourceType, sourceExternalId, sourceSequence])
  @@index([affiliateUserId, state])
  @@index([affiliateUserId, availableAt])
  @@index([referredUserId])
  @@map("affiliate_earnings")
}

model AffiliatePayoutRequest {
  id              String @id @default(uuid())
  affiliateUserId String @map("affiliate_user_id")

  type   AffiliatePayoutType
  status AffiliatePayoutStatus @default(REQUESTED)

  currency    AffiliateCurrency @default(USD)
  amountCents Int               @map("amount_cents")

  // For cashout
  walletAddress String? @map("wallet_address")
  chain         String  @default("polygon")
  token         String  @default("usdt")
  txHash        String? @map("tx_hash")

  // Ops
  adminNote String? @map("admin_note")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  affiliateUser User @relation(fields: [affiliateUserId], references: [id], onDelete: Cascade)

  @@index([affiliateUserId, status])
  @@index([affiliateUserId, type, status])
  @@map("affiliate_payout_requests")
}

model UserAttribution {
  id           String   @id @default(cuid())
  userId       String   @unique @map("user_id")
  firstTouchAt DateTime @default(now()) @map("first_touch_at")

  // First touch landing info (privacy-friendly)
  landingPath String? @map("landing_path")
  refHost     String? @map("ref_host")
  refType     String? @map("ref_type") // "search" | "referral" | "direct" | "unknown"
  channel     String? @map("channel") // "organic_search" | "paid_search" | "referral" | "direct" | "unknown"

  // Snapshot of attribution params at first touch
  utmSource   String? @map("utm_source")
  utmMedium   String? @map("utm_medium")
  utmCampaign String? @map("utm_campaign")
  utmTerm     String? @map("utm_term")
  utmContent  String? @map("utm_content")
  adClickId   String? @map("ad_click_id")
  adClickProv String? @map("ad_click_provider")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@index([firstTouchAt])
  @@index([channel])
  @@index([refHost])
  @@map("user_attributions")
}

model UserOffer {
  id         String         @id @default(cuid())
  userId     String         @map("user_id")
  type       UserOfferType
  state      UserOfferState @default(ACTIVE)
  source     String?
  variant    String?
  startedAt  DateTime?      @map("started_at")
  endsAt     DateTime?      @map("ends_at")
  consumedAt DateTime?      @map("consumed_at")
  createdAt  DateTime       @default(now()) @map("created_at")
  updatedAt  DateTime       @updatedAt @map("updated_at")

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

  @@unique([userId, type])
  @@index([type, state])
  @@index([userId])
  @@index([endsAt])
  @@map("user_offers")
}

model UserStats {
  id                   String    @id @default(cuid())
  userId               String    @unique @map("user_id")
  totalPaidCents       Int       @default(0) @map("total_paid_cents")
  ordersCount          Int       @default(0) @map("orders_count")
  firstPaidAt          DateTime? @map("first_paid_at")
  lastPaidAt           DateTime? @map("last_paid_at")
  hasUsedProTrial      Boolean   @default(false) @map("has_used_pro_trial")
  proTrialSource       String?   @map("pro_trial_source")
  proTrialConverted    Boolean   @default(false) @map("pro_trial_converted")
  hitPaywallCount      Int       @default(0) @map("hit_paywall_count")
  canBypassBlur        Boolean   @default(false) @map("can_bypass_blur")
  createdAt            DateTime  @default(now()) @map("created_at")
  updatedAt            DateTime  @updatedAt @map("updated_at")
  user                 User      @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@map("user_stats")
}

model GitHubConnection {
  id             String   @id @default(cuid())
  userId         String   @unique @map("user_id")
  githubUserId   String   @map("github_user_id")
  githubUsername String   @map("github_username")
  accessToken    String   @map("access_token")
  scope          String
  createdAt      DateTime @default(now()) @map("created_at")
  updatedAt      DateTime @updatedAt @map("updated_at")
  user           User     @relation(fields: [userId], references: [id], onDelete: Cascade)

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

model GitHubRepository {
  id            String    @id @default(cuid())
  userId        String    @map("user_id")
  owner         String
  name          String
  fullName      String    @map("full_name")
  url           String
  defaultBranch String    @default("main") @map("default_branch")
  isPrivate     Boolean   @default(false) @map("is_private")
  description   String?
  language      String?
  indexStatus   String    @default("pending") @map("index_status")
  indexedAt     DateTime? @map("indexed_at")
  lastSyncedAt  DateTime? @map("last_synced_at")
  metadata      Json?
  createdAt     DateTime  @default(now()) @map("created_at")
  updatedAt     DateTime  @updatedAt @map("updated_at")
  user          User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  projects      Project[]

  @@unique([userId, owner, name])
  @@index([userId])
  @@index([userId, indexStatus])
  @@index([fullName])
  @@map("github_repositories")
}

model Agent {
  id           String      @id @default(cuid())
  name         String
  description  String?
  instructions String
  model        String      @default("google/gemini-2.5-pro")
  avatar       String?
  color        String?
  tools        String[]    @default([])
  enabled      Boolean     @default(true)
  isSystem     Boolean     @default(false) @map("is_system")
  createdAt    DateTime    @default(now()) @map("created_at")
  updatedAt    DateTime    @updatedAt @map("updated_at")
  userAgents   UserAgent[]

  @@index([isSystem, enabled])
  @@map("agents")
}

model UserAgent {
  id                 String        @id @default(cuid())
  userId             String        @map("user_id")
  agentId            String        @map("agent_id")
  isDefault          Boolean       @default(false) @map("is_default")
  enabled            Boolean       @default(true)
  customInstructions String?       @map("custom_instructions")
  customModel        String?       @map("custom_model")
  createdAt          DateTime      @default(now()) @map("created_at")
  updatedAt          DateTime      @updatedAt @map("updated_at")
  interactions       Interaction[]
  agent              Agent         @relation(fields: [agentId], references: [id], onDelete: Cascade)
  user               User          @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([userId, agentId])
  @@index([userId])
  @@index([userId, isDefault])
  @@index([agentId])
  @@map("user_agents")
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
  @@map("verification_tokens")
}

model ImageAsset {
  id           String       @id @default(cuid())
  userId       String       @map("user_id")
  agentId      String?      @map("agent_id")
  projectId    String?      @map("project_id")
  storageKey   String       @unique @map("storage_key")
  imageUrl     String       @map("image_url")
  thumbnailUrl String?      @map("thumbnail_url")
  contentType  String       @default("image/png") @map("content_type")
  fileSize     Int?         @map("file_size")
  width        Int?
  height       Int?
  type         String       @default("generate")
  prompt       String
  model        String       @default("doubao-seedream-4-0-250828")
  parentId     String?      @map("parent_id")
  size         String?
  ratio        String?
  watermark    Boolean      @default(false)
  status       String       @default("completed")
  error        String?
  tags         String[]     @default([])
  createdAt    DateTime     @default(now()) @map("created_at")
  updatedAt    DateTime     @updatedAt @map("updated_at")
  parent       ImageAsset?  @relation("ImageVersions", fields: [parentId], references: [id])
  children     ImageAsset[] @relation("ImageVersions")
  project      Project?     @relation(fields: [projectId], references: [id], onDelete: Cascade)
  user         User         @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@index([projectId, createdAt])
  @@index([parentId])
  @@index([prompt])
  @@map("image_assets")
}

model ActivationCode {
  id         String    @id @default(cuid())
  code       String    @unique
  userId     String    @map("user_id")
  createdBy  String    @map("created_by")
  assignedTo String?   @map("assigned_to")
  status     String    @default("unused")
  usedBy     String?   @map("used_by")
  usedAt     DateTime? @map("used_at")
  projectId  String?   @map("project_id")
  expiresAt  DateTime? @map("expires_at")
  createdAt  DateTime  @default(now()) @map("created_at")
  updatedAt  DateTime  @updatedAt @map("updated_at")
  user       User      @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([status])
  @@index([userId])
  @@index([code])
  @@map("activation_codes")
}

model Project {
  id                 String               @id @default(cuid())
  userId             String               @map("user_id")
  name               String
  description        String?
  status             String               @default("active")
  metadata           Json?
  createdAt          DateTime             @default(now()) @map("created_at")
  updatedAt          DateTime             @updatedAt @map("updated_at")
  githubRepositoryId String?              @map("github_repository_id")
  conversations      Conversation[]
  documents          Document[]
  imageAssets        ImageAsset[]
  qualityScore       ProjectQualityScore?
  githubRepository   GitHubRepository?    @relation(fields: [githubRepositoryId], references: [id])
  user               User                 @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
  @@index([status])
  @@index([userId, status])
  @@index([githubRepositoryId])
  @@map("projects")
}

model Document {
  id          String     @id @default(cuid())
  projectId   String     @map("project_id")
  userId      String     @map("user_id")
  title       String
  content     String
  fileUrl     String?    @map("file_url")
  fileType    String?    @map("file_type")
  fileSize    Int?       @map("file_size")
  status      String     @default("active")
  metadata    Json?
  version     Int        @default(1)
  isCurrent   Boolean    @default(true) @map("is_current")
  contentHash String?    @map("content_hash")
  parentId    String?    @map("parent_id")
  createdAt   DateTime   @default(now()) @map("created_at")
  updatedAt   DateTime   @updatedAt @map("updated_at")
  parent      Document?  @relation("DocumentVersions", fields: [parentId], references: [id])
  children    Document[] @relation("DocumentVersions")
  project     Project    @relation(fields: [projectId], references: [id], onDelete: Cascade)
  user        User       @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([projectId])
  @@index([userId])
  @@index([projectId, title, isCurrent])
  @@index([projectId, title, version])
  @@index([parentId])
  @@map("documents")
}

model ProjectQualityScore {
  id         String   @id @default(cuid())
  projectId  String   @unique @map("project_id")
  totalScore Int
  confidence String
  metrics    Json
  stats      Json?
  version    Int      @default(1)
  scoredAt   DateTime @default(now()) @map("scored_at")
  updatedAt  DateTime @updatedAt @map("updated_at")
  project    Project  @relation(fields: [projectId], references: [id], onDelete: Cascade)

  @@index([scoredAt])
  @@index([totalScore])
  @@map("project_quality_scores")
}

model Product {
  id                  String                      @id @default(uuid())
  name                String
  description         Json?
  price               Int                         // USD price in cents (default/fallback)
  originalPrice       Int                         @default(0) @map("original_price")
  currency            String                      @default("usd") // Default currency (kept for backward compatibility)
  type                ProductType
  interval            String?
  status              ProductStatus               @default(ACTIVE)
  isAvailable         Boolean                     @default(true) @map("is_available")
  sortOrder           Int                         @default(0) @map("sort_order")
  metadata            Json?
  createdAt           DateTime                    @default(now()) @map("created_at")
  updatedAt           DateTime                    @updatedAt @map("updated_at")
  deletedAt           DateTime?                   @map("deleted_at")
  hasTrial            Boolean                     @default(false) @map("has_trial")
  trialCreditsAmount  Int?                        @map("trial_credits_amount")
  trialDays           Int?                        @map("trial_days")
  orders              Order[]
  creditsPackage      ProductCreditsPackage?
  oneTimeEntitlements ProductOneTimeEntitlement[]
  productSubscription ProductSubscription?
  prices              ProductPrice[]              // Multi-currency prices

  @@index([status, isAvailable, deletedAt])
  @@index([type, status, deletedAt])
  @@map("products")
}

// Multi-currency pricing for international markets
// EUR for Eurozone, GBP for UK, USD as default fallback
model ProductPrice {
  id             String   @id @default(uuid())
  productId      String   @map("product_id")
  currency       String   // ISO 4217: "USD", "EUR", "GBP", "CHF"
  amount         Int      // Price in smallest currency unit (cents/pence)
  originalAmount Int      @default(0) @map("original_amount") // For discount display
  createdAt      DateTime @default(now()) @map("created_at")
  updatedAt      DateTime @updatedAt @map("updated_at")
  product        Product  @relation(fields: [productId], references: [id], onDelete: Cascade)

  @@unique([productId, currency])
  @@index([currency])
  @@map("product_prices")
}

model Order {
  id              String    @id @default(uuid())
  userId          String    @map("user_id")
  productId       String    @map("product_id")
  productSnapshot Json      @map("product_snapshot")
  type            String    @default("NEW_PURCHASE")
  status          String    @default("PENDING")
  amount          Int
  quantity        Int       @default(1)
  currency        String    @default("usd")
  expiresAt       DateTime  @map("expires_at")
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")
  deletedAt       DateTime? @map("deleted_at")
  product         Product   @relation(fields: [productId], references: [id])
  user            User      @relation("UserOrders", fields: [userId], references: [id])
  payments        Payment[]

  @@index([userId])
  @@index([productId])
  @@index([status])
  @@map("orders")
}

model Payment {
  id                    String    @id @default(uuid())
  orderId               String    @map("order_id")
  userId                String    @map("user_id")
  amount                Int
  currency              String    @default("usd")
  status                String    @default("PENDING")
  paymentGateway        String    @default("STRIPE") @map("payment_gateway")
  paymentUrl            String?   @map("payment_url")
  gatewayTransactionId  String?   @map("gateway_transaction_id")
  gatewaySubscriptionId String?   @map("gateway_subscription_id")
  isSubscription        Boolean   @default(false) @map("is_subscription")
  gatewayResponse       Json?     @map("gateway_response")
  extra                 Json?     @default("{}")
  expiresAt             DateTime  @map("expires_at")
  createdAt             DateTime  @default(now()) @map("created_at")
  updatedAt             DateTime  @updatedAt @map("updated_at")
  deletedAt             DateTime? @map("deleted_at")
  order                 Order     @relation(fields: [orderId], references: [id])
  user                  User      @relation("UserPayments", fields: [userId], references: [id])

  @@index([orderId])
  @@index([userId])
  @@index([status])
  @@index([gatewayTransactionId])
  @@map("payments")
}

model PaymentWebhookLog {
  id          String    @id @default(uuid())
  gateway     String
  eventId     String    @map("event_id")
  eventType   String    @map("event_type")
  status      String    @default("RECEIVED")
  payload     Json
  error       String?
  processedAt DateTime? @map("processed_at")
  createdAt   DateTime  @default(now()) @map("created_at")

  @@unique([gateway, eventId])
  @@index([gateway])
  @@index([eventType])
  @@index([status])
  @@index([createdAt])
  @@map("payment_webhook_logs")
}

// Immutable cashflow facts for analytics & reconciliation.
// - Append-only: do NOT update rows; add new rows for refunds/disputes.
// - Use (gateway, externalId) as idempotency key to support safe replays/backfills.
model PaymentTransaction {
  id                    String   @id @default(uuid())
  gateway               String
  externalId            String   @map("external_id") // invoice id / payment_intent id / charge id
  kind                  String   // e.g. ONE_OFF_CHARGE, SUBSCRIPTION_INITIAL_CHARGE, SUBSCRIPTION_RENEWAL_CHARGE, REFUND, DISPUTE
  amount                Int      // in smallest unit (cents)
  currency              String   @default("usd")
  userId                String?  @map("user_id")
  orderId               String?  @map("order_id")
  paymentId             String?  @map("payment_id")
  gatewaySubscriptionId String?  @map("gateway_subscription_id")
  gatewayInvoiceId      String?  @map("gateway_invoice_id")
  gatewayChargeId       String?  @map("gateway_charge_id")
  gatewayPaymentIntentId String? @map("gateway_payment_intent_id")
  occurredAt            DateTime @map("occurred_at") // gateway-side occurrence time
  sourceEventId         String?  @map("source_event_id")
  sourceEventType       String?  @map("source_event_type")
  createdAt             DateTime @default(now()) @map("created_at") // inserted-at time

  @@unique([gateway, externalId])
  @@index([userId])
  @@index([occurredAt])
  @@index([gatewaySubscriptionId])
  @@index([gatewayInvoiceId])
  @@index([gatewayChargeId])
  @@index([gatewayPaymentIntentId])
  @@index([orderId])
  @@index([paymentId])
  @@map("payment_transactions")
}

// Billing accounts/records/freeze_records have been migrated to Velobase (external billing service).
// The billing_billing_accounts, billing_billing_records, and billing_billing_freeze_records tables
// can be dropped after verifying all data has been migrated. See docs/integrations/billing/.

model UserSubscription {
  id                    String                  @id @default(uuid())
  userId                String                  @map("user_id")
  planId                String                  @map("plan_id")
  planSnapshot          Json                    @map("plan_snapshot")
  status                UserSubscriptionStatus  @default(UNDEFINED)
  gateway               String                  @default("")
  gatewaySubscriptionId String                  @default("") @map("gateway_subscription_id")
  cancelAtPeriodEnd     Boolean                 @default(false) @map("cancel_at_period_end")
  canceledAt            DateTime?               @map("canceled_at")
  endedAt               DateTime?               @map("ended_at")
  createdAt             DateTime                @default(now()) @map("created_at")
  updatedAt             DateTime                @updatedAt @map("updated_at")
  deletedAt             DateTime?               @map("deleted_at")
  cycles                UserSubscriptionCycle[]

  @@index([userId, status])
  @@map("membership_user_subscriptions")
}

model UserSubscriptionCycle {
  id                    String                      @id @default(uuid())
  subscriptionId        String                      @map("subscription_id")
  paymentId             String?                     @map("payment_id")
  // 幂等键：用于防止并发/重复履约导致重复创建 cycle（不依赖 paymentId 语义）
  // - 首期/Trial：建议使用 `pay_${paymentId}`
  // - Stripe 续费：建议使用 `stripe_invoice_${txnId}` 或 `sub_renewal_${subscriptionId}_${txnId}`
  uniqueKey             String?                     @unique @map("unique_key")
  type                  UserSubscriptionCycleType   @default(UNDEFINED)
  status                UserSubscriptionCycleStatus @default(UNDEFINED)
  sequenceNumber        Int                         @default(1) @map("sequence_number")
  startsAt              DateTime                    @map("starts_at")
  expiresAt             DateTime                    @map("expires_at")
  lastCreditGrantAnchor DateTime?                   @map("last_credit_grant_anchor")
  createdAt             DateTime                    @default(now()) @map("created_at")
  updatedAt             DateTime                    @updatedAt @map("updated_at")
  deletedAt             DateTime?                   @map("deleted_at")
  subscription          UserSubscription            @relation(fields: [subscriptionId], references: [id])

  @@unique([subscriptionId, sequenceNumber])
  @@index([subscriptionId])
  @@index([paymentId])
  @@index([uniqueKey])
  @@index([lastCreditGrantAnchor])
  @@map("membership_user_subscription_cycles")
}

model UserEntitlement {
  id            String                    @id @default(uuid())
  userId        String                    @map("user_id")
  entitlementId String                    @map("entitlement_id")
  sourceType    UserEntitlementSourceType @default(UNDEFINED) @map("source_type")
  sourceId      String                    @map("source_id")
  value         Json                      @map("value")
  status        UserEntitlementStatus     @default(UNDEFINED)
  startsAt      DateTime                  @map("starts_at")
  expiresAt     DateTime                  @map("expires_at")
  createdAt     DateTime                  @default(now()) @map("created_at")
  updatedAt     DateTime                  @updatedAt @map("updated_at")
  deletedAt     DateTime?                 @map("deleted_at")
  revokedAt     DateTime?                 @map("revoked_at")

  @@unique([userId, entitlementId, sourceType, sourceId])
  @@index([userId, status, expiresAt])
  @@map("membership_user_entitlements")
}

model SubscriptionPlan {
  id                   String                   @id @default(uuid())
  type                 SubscriptionPlanType     @default(UNDEFINED)
  name                 String                   @default("")
  status               SubscriptionPlanStatus   @default(UNDEFINED)
  interval             SubscriptionPlanInterval @default(UNDEFINED)
  intervalCount        Int                      @default(1) @map("interval_count")
  creditsPerPeriod     Int                      @default(0) @map("credits_per_period")
  /// @deprecated Use creditsPerPeriod instead. Kept for backward compatibility.
  creditsPerMonth      Int                      @default(0) @map("credits_per_month")
  createdAt            DateTime                 @default(now()) @map("created_at")
  updatedAt            DateTime                 @updatedAt @map("updated_at")
  deletedAt            DateTime?                @map("deleted_at")
  planEntitlements     PlanEntitlement[]
  productSubscriptions ProductSubscription[]

  @@index([type])
  @@index([status])
  @@index([interval])
  @@map("product_subscription_plans")
}

model ProductSubscription {
  id        String           @id @default(uuid())
  productId String           @unique @map("product_id")
  planId    String           @map("plan_id")
  createdAt DateTime         @default(now()) @map("created_at")
  updatedAt DateTime         @updatedAt @map("updated_at")
  deletedAt DateTime?        @map("deleted_at")
  plan      SubscriptionPlan @relation(fields: [planId], references: [id])
  product   Product          @relation(fields: [productId], references: [id])

  @@index([planId])
  @@map("product_product_subscriptions")
}

model ProductCreditsPackage {
  id            String    @id @default(uuid())
  productId     String    @unique @map("product_id")
  creditsAmount Int       @default(0) @map("credits_amount")
  createdAt     DateTime  @default(now()) @map("created_at")
  updatedAt     DateTime  @updatedAt @map("updated_at")
  deletedAt     DateTime? @map("deleted_at")
  product       Product   @relation(fields: [productId], references: [id])

  @@map("product_product_credits_packages")
}

model Entitlement {
  id                         String                      @id @default(uuid())
  key                        String                      @unique
  type                       EntitlementType             @default(UNDEFINED)
  status                     EntitlementStatus           @default(UNDEFINED)
  name                       String                      @default("")
  description                String                      @default("")
  createdAt                  DateTime                    @default(now()) @map("created_at")
  updatedAt                  DateTime                    @updatedAt @map("updated_at")
  deletedAt                  DateTime?                   @map("deleted_at")
  planEntitlements           PlanEntitlement[]
  productOneTimeEntitlements ProductOneTimeEntitlement[]

  @@index([type])
  @@index([status])
  @@map("product_entitlements")
}

model PlanEntitlement {
  id            String           @id @default(uuid())
  planId        String           @map("plan_id")
  entitlementId String           @map("entitlement_id")
  value         Json             @map("value")
  createdAt     DateTime         @default(now()) @map("created_at")
  updatedAt     DateTime         @updatedAt @map("updated_at")
  deletedAt     DateTime?        @map("deleted_at")
  entitlement   Entitlement      @relation(fields: [entitlementId], references: [id])
  plan          SubscriptionPlan @relation(fields: [planId], references: [id])

  @@unique([planId, entitlementId])
  @@index([planId])
  @@index([entitlementId])
  @@map("product_plan_entitlements")
}

model ProductOneTimeEntitlement {
  id            String      @id @default(uuid())
  productId     String      @map("product_id")
  entitlementId String      @map("entitlement_id")
  value         Json        @map("value")
  durationDays  Int         @default(0) @map("duration_days")
  createdAt     DateTime    @default(now()) @map("created_at")
  updatedAt     DateTime    @updatedAt @map("updated_at")
  deletedAt     DateTime?   @map("deleted_at")
  entitlement   Entitlement @relation(fields: [entitlementId], references: [id])
  product       Product     @relation(fields: [productId], references: [id])

  @@index([productId])
  @@index([entitlementId])
  @@map("product_product_one_time_entitlements")
}

model Task {
  id           String     @id @default(uuid())
  userId       String     @map("user_id")
  taskType     TaskType   @default(UNDEFINED) @map("task_type")
  status       TaskStatus @default(UNDEFINED)
  priority     Int        @default(0)
  requestData  Json?      @map("request_data")
  resultData   Json?      @map("result_data")
  errorMessage String?    @map("error_message")
  retryCount   Int        @default(0) @map("retry_count")
  extra        Json?      @default("{}")
  startedAt    DateTime?  @map("started_at")
  completedAt  DateTime?  @map("completed_at")
  createdAt    DateTime   @default(now()) @map("created_at")
  updatedAt    DateTime   @updatedAt @map("updated_at")
  deletedAt    DateTime?  @map("deleted_at")

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

model PromoCode {
  id            String                @id @default(uuid())
  code          String                @unique @map("code")
  codeType      PromoCodeType         @default(UNDEFINED) @map("code_type")
  status        PromoCodeStatus       @default(ACTIVE)
  grantType     PromoGrantType        @default(CREDIT) @map("grant_type")
  creditsAmount Int                   @default(0) @map("credits_amount")
  productId     String?               @map("product_id")
  usageLimit    Int                   @default(0) @map("usage_limit")
  usedCount     Int                   @default(0) @map("used_count")
  perUserLimit  Int                   @default(1) @map("per_user_limit")
  startsAt      DateTime?             @map("starts_at")
  expiresAt     DateTime?             @map("expires_at")
  notes         String?               @map("notes")
  createdBy     String                @default("admin") @map("created_by")
  createdAt     DateTime              @default(now()) @map("created_at")
  updatedAt     DateTime              @updatedAt @map("updated_at")
  deletedAt     DateTime?             @map("deleted_at")
  redemptions   PromoCodeRedemption[]

  @@index([codeType])
  @@index([status])
  @@index([expiresAt])
  @@index([productId])
  @@map("promo_codes")
}

model PromoCodeRedemption {
  id               String    @id @default(uuid())
  promoCodeId      String    @map("promo_code_id")
  userId           String    @map("user_id")
  billingAccountId String?   @map("billing_account_id")
  billingRecordId  String?   @map("billing_record_id")
  creditsGranted   Int       @default(0) @map("credits_granted")
  redeemedAt       DateTime  @default(now()) @map("redeemed_at")
  ipAddress        String?   @map("ip_address")
  userAgent        String?   @map("user_agent")
  createdAt        DateTime  @default(now()) @map("created_at")
  promoCode        PromoCode @relation(fields: [promoCodeId], references: [id])

  @@index([promoCodeId])
  @@index([userId])
  @@unique([promoCodeId, userId])
  @@map("promo_code_redemptions")
}

enum ProductType {
  UNDEFINED
  SUBSCRIPTION
  ONE_TIME_ENTITLEMENT
  CREDITS_PACKAGE
}

enum ProductStatus {
  UNDEFINED
  ACTIVE
  INACTIVE
}

// Local billing tables and enums have been migrated to Velobase.
// Current local type definitions are in src/server/billing/types/.

enum UserSubscriptionStatus {
  UNDEFINED
  INCOMPLETE
  INCOMPLETE_EXPIRED
  TRIALING
  ACTIVE
  PAST_DUE
  UNPAID
  PAUSED
  CANCELED
}

enum UserSubscriptionCycleType {
  UNDEFINED
  REGULAR
  TRIAL
}

enum UserSubscriptionCycleStatus {
  UNDEFINED
  ACTIVE
  CLOSED
}

enum UserEntitlementSourceType {
  UNDEFINED
  SUBSCRIPTION_CYCLE
  ORDER
  PROMOTION
}

enum UserEntitlementStatus {
  UNDEFINED
  ACTIVE
  EXPIRED
  REVOKED
}

enum SubscriptionPlanType {
  UNDEFINED
  FREE
  STARTER
  PLUS
  PREMIUM
}

enum SubscriptionPlanStatus {
  UNDEFINED
  ACTIVE
  INACTIVE
}

enum SubscriptionPlanInterval {
  UNDEFINED
  MONTH
  YEAR
  WEEK
}

enum EntitlementType {
  UNDEFINED
  BOOLEAN
  LIMIT
  LEVEL
}

enum EntitlementStatus {
  UNDEFINED
  ACTIVE
  INACTIVE
}

enum TaskType {
  UNDEFINED
  SYNC
  ASYNC
}

enum TaskStatus {
  UNDEFINED
  PENDING
  RUNNING
  COMPLETED
  FAILED
  CANCELLED
}

enum PromoCodeType {
  UNDEFINED
  KOL_INTERNAL
  USER_PROMOTION
}

enum PromoCodeStatus {
  UNDEFINED
  ACTIVE
  DISABLED
  EXPIRED
}

enum PromoGrantType {
  UNDEFINED
  CREDIT
  PRODUCT
}

// ============================================================================
// Touch (Reach-out) / Notifications scheduling
// ============================================================================

enum TouchChannel {
  EMAIL
  SMS
  PUSH
}

enum TouchTriggerType {
  SCHEDULED // 定时触发
  EVENT     // 事件触发
  MANUAL    // 手动触发
}

enum TouchScheduleStatus {
  PENDING
  PROCESSING
  SENT
  CANCELLED
  SUPERSEDED
  FAILED
}

enum TouchRecordStatus {
  SENT
  DELIVERED
  DELIVERY_DELAYED
  BOUNCED
  COMPLAINED
  OPENED
  CLICKED
  FAILED
}

// 触达场景（Campaign）- 定义触达的业务场景
model TouchScene {
  id          String           @id @default(uuid())
  key         String           @unique // e.g. "sub_renewal_d1", "welcome_email"
  name        String
  description String?
  channel     TouchChannel     @default(EMAIL)
  triggerType TouchTriggerType @default(SCHEDULED) @map("trigger_type")
  isActive    Boolean          @default(true) @map("is_active")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  templates TouchTemplate[]
  schedules TouchSchedule[]

  @@index([key, isActive])
  @@map("touch_scenes")
}

// 触达模板 - 场景下的具体内容模板（支持多语言/多版本/A-B测试）
model TouchTemplate {
  id       String @id @default(uuid())
  sceneKey String @map("scene_key")

  locale    String  @default("en") // en, zh, ja, etc.
  version   String  @default("default") // default, v2, variant_a, etc.
  isDefault Boolean @default(false) @map("is_default")
  isActive  Boolean @default(true) @map("is_active")

  // 邮件模板内容
  subject  String?
  bodyText String? @map("body_text")
  bodyHtml String? @map("body_html")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  scene   TouchScene    @relation(fields: [sceneKey], references: [key], onDelete: Cascade)
  records TouchRecord[]

  @@unique([sceneKey, locale, version])
  @@index([sceneKey, isActive, isDefault])
  @@map("touch_templates")
}

// 触达计划 - 待发送的具体任务
model TouchSchedule {
  id      String       @id @default(uuid())
  userId  String       @map("user_id")
  channel TouchChannel

  // 关联场景
  sceneKey String?     @map("scene_key")
  scene    TouchScene? @relation(fields: [sceneKey], references: [key])

  referenceType String @map("reference_type")
  referenceId   String @map("reference_id")

  // Idempotency / dedupe
  dedupeKey String @unique @map("dedupe_key")

  timezone      String?             @map("timezone")
  scheduledAt   DateTime            @map("scheduled_at")
  nextAttemptAt DateTime            @map("next_attempt_at")
  status        TouchScheduleStatus @default(PENDING)

  attemptCount Int       @default(0) @map("attempt_count")
  maxAttempts  Int       @default(5) @map("max_attempts")
  lockedAt     DateTime? @map("locked_at")
  lockId       String?   @map("lock_id")
  lastError    String?   @map("last_error")

  payload Json? @map("payload")

  sentAt       DateTime? @map("sent_at")
  cancelledAt  DateTime? @map("cancelled_at")
  supersededAt DateTime? @map("superseded_at")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@index([sceneKey])
  @@index([status, nextAttemptAt])
  @@index([referenceType, referenceId])
  @@index([userId, createdAt])
  @@map("touch_schedules")
}

// 触达日志 - 发送记录
model TouchRecord {
  id            String @id @default(uuid())
  scheduleId    String @map("schedule_id")
  attemptNumber Int    @map("attempt_number")

  // 使用的模板
  templateId String? @map("template_id")

  provider          String  @default("resend") @map("provider")
  providerMessageId String? @map("provider_message_id")
  toEmail           String  @map("to_email")
  subject           String? @map("subject")

  status TouchRecordStatus
  error  String?           @map("error")
  meta   Json?             @map("meta")

  occurredAt DateTime @default(now()) @map("occurred_at")
  createdAt  DateTime @default(now()) @map("created_at")

  schedule TouchSchedule  @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
  template TouchTemplate? @relation(fields: [templateId], references: [id], onDelete: SetNull)

  @@unique([scheduleId, attemptNumber])
  @@unique([provider, providerMessageId])
  @@index([scheduleId, occurredAt])
  @@index([templateId])
  @@map("touch_records")
}

// ============================================================================
// User Notification Preferences - 用户通知偏好
// ============================================================================

enum NotificationType {
  MARKETING_PROMO // 营销/促销邮件
  PRODUCT_UPDATE  // 产品更新通知
  NEWSLETTER      // 订阅内容/周报
  BILLING_ALERT   // 账单提醒 (通常强制)
  SYSTEM_NOTICE   // 系统通知
}

model UserNotificationPreference {
  id     String           @id @default(uuid())
  userId String           @map("user_id")
  type   NotificationType

  emailEnabled Boolean @default(true) @map("email_enabled")

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@unique([userId, type])
  @@index([type, emailEnabled])
  @@map("user_notification_preferences")
}

// ============================================================================
// Support System - AI 客服助理
// ============================================================================

enum SupportTicketStatus {
  OPEN           // 待处理 (Initial state)
  NEEDS_APPROVAL // 待审核 (AI proposed draft/action)
  WAITING        // 待回复 (Waiting for customer)
  SOLVED         // 已解决 (Done)
}

enum SupportActorType {
  USER   // 客户
  AI     // 模型
  AGENT  // 人工
  SYSTEM // 系统
}

enum SupportEventType {
  MESSAGE // 实际发生的对话 (Email, Chat)
  DRAFT   // 拟定的回复/操作 (Internal only)
  ACTION  // 工具调用 (Refund, Cancel, etc.)
  NOTE    // 内部备注
  SYSTEM  // 状态变更日志
}

model SupportTicket {
  id String @id @default(uuid())

  // 1. 客户身份
  userId  String? @map("user_id")
  contact String  @map("contact") // email or handle
  channel String  @default("email")

  // 2. 状态机
  status     SupportTicketStatus @default(OPEN)
  assignedTo SupportActorType    @default(AI) @map("assigned_to")

  // 3. 上下文
  subject  String?
  summary  String? @db.Text
  priority Int     @default(0) // 0:Normal, 1:High, 2:Urgent

  // 4. 渠道绑定（用于投影到外部系统）
  feishuThreadId String? @map("feishu_thread_id") // 飞书话题 ID

  // 5. AI 对话上下文（复用 ai-chat 的 Conversation）
  conversationId String?       @map("conversation_id")
  conversation   Conversation? @relation(fields: [conversationId], references: [id])

  // 6. 统计
  createdAt  DateTime  @default(now()) @map("created_at")
  updatedAt  DateTime  @updatedAt @map("updated_at")
  resolvedAt DateTime? @map("resolved_at")

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

  @@index([status, assignedTo])
  @@index([contact])
  @@index([feishuThreadId])
  @@map("support_tickets")
}

model SupportTimeline {
  id       String @id @default(uuid())
  ticketId String @map("ticket_id")

  // 1. 主体
  actor   SupportActorType
  actorId String?          @map("actor_id") // userId if AGENT/USER

  // 2. 事件
  type    SupportEventType
  content String?          @db.Text // The message body, draft content, or action desc

  // 3. 元数据 (The "Everything Else" bucket)
  // DRAFT: { proposed_actions: [...], confidence: 0.8 }
  // MESSAGE (Email): { messageId, inReplyTo, cc }
  // ACTION: { tool_name, args, result }
  metadata Json?

  createdAt DateTime @default(now()) @map("created_at")

  ticket SupportTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)

  @@index([ticketId, createdAt])
  @@index([ticketId, type])
  @@map("support_timeline")
}

model SupportSyncCursor {
  id        String   @id // e.g., "email_inbox"
  lastUid   Int      @default(0) @map("last_uid")
  updatedAt DateTime @updatedAt @map("updated_at")

  @@map("support_sync_cursors")
}
