generator client {
  provider      = "prisma-client-js"
  // native = local dev (Windows/macOS/Linux). rhel-openssl-3.0.x = the Amazon
  // Linux runtime Netlify Functions run on. Without it, dynamic API routes
  // 500 with "could not locate Query Engine for runtime rhel-openssl-3.0.x".
  binaryTargets = ["native", "rhel-openssl-3.0.x"]
}

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

// ============================================================
// INFRASTRUCTURE
// ============================================================

model RateLimit {
  key          String   @id
  count        Int      @default(1)
  window_start DateTime @default(now()) @db.Timestamptz(6)

  @@index([window_start])
  @@map("rate_limits")
}

// ============================================================
// ENUMS
// ============================================================

enum UserType {
  admin
  developer
  escort
  strip_club
  user
  streamer
}

enum TransactionType {
  SUBSCRIPTION
  PURCHASE
  WITHDRAWAL
  TIP
  // R16 B.4: Stripe refund / chargeback reversal.
  REFUND
}

// ============================================================
// CORE MODELS
// ============================================================

model User {
  id                Int       @id @default(autoincrement())
  id_aw             String?   @unique
  user_type         UserType  @default(user)
  is_verified       Boolean   @default(false)
  is_vip            Boolean   @default(false)
  is_top            Boolean   @default(false)
  is_old            Boolean   @default(false)
  is_agency         Boolean   @default(false)
  is_pornstar       Boolean   @default(false)
  hits              Int       @default(0)
  username          String?
  email             String    @unique
  email_verified_at DateTime?
  email_hash        String?
  country_id        Int?
  city_id           Int?
  password          String?
  ip_address        String?
  currency_id       Int?
  consent_policies  Boolean   @default(false)
  consent_emails    Boolean   @default(false)
  consent_gdpr      Boolean   @default(false)
  lastonline_at     DateTime?
  profile_photo     String?
  languages         String?
  bio                  String?   @db.Text
  services_description String?   @db.Text
  status               String?
  private           Boolean   @default(false)
  active            Boolean   @default(true)
  gender            String?
  born_at           DateTime?
  remember_token          String?
  verification_token_sent_at DateTime? @db.Timestamptz(6)
  sessions_revoked_at     DateTime?  @db.Timestamptz(6)
  auto_reply              Json?
  totp_secret             String?
  totp_enabled            Boolean    @default(false)
  banned_at               DateTime?
  ban_reason              String?
  suspended_until         DateTime?
  verified_age_at         DateTime?
  age_verification_method String?
  notification_preferences Json?
  created_at        DateTime  @default(now())
  updated_at        DateTime  @updatedAt

  // Relations
  country        Country?        @relation(fields: [country_id], references: [id])
  city           City?           @relation(fields: [city_id], references: [id])
  currency       Currency?       @relation(fields: [currency_id], references: [id])
  credit         Credit?
  step           Step?
  characteristic Characteristic?
  coordinate     Coordinate?

  galleries      Gallery[]
  photos         Photo[]
  videos         Video[]
  blogs          Blog[]
  eroticas       Erotica[]
  faqs           Faq[]
  feeds          Feed[]
  reviews        Review[]
  notifications  Notification[]
  wowzaStreams   WowzaStream[]
  purchases      Purchase[]
  hits_records   Hit[]

  // Subscriptions
  subscriptions         Subscription[]  @relation("subscriber")
  escortSubscriptions   Subscription[]  @relation("subscribed_escort")
  escortSubscription    EscortSubscription?

  // Transactions
  transactionsSent     Transaction[] @relation("transaction_sender")
  transactionsReceived Transaction[] @relation("transaction_receiver")

  // Pivot relations
  rateUsers      RateUser[]
  enjoyUsers     EnjoyUser[]
  piercings      Piercing[]
  tattoos        Tattoo[]
  languageLinks  Language[]
  alternatives   Alternative[]
  offers         Offer[]
  withs          With[]

  // Messaging
  messages       Message[]
  rooms          Room[]

  // Search
  searches       Search[]

  // Favorites (polymorphic - stored in favorites table)
  favorites      Favorite[]

  // Blocking
  blockedCountries BlockedCountry[]
  phones           Phone[]
  availabilities   Availability[]
  reports          Report[]

  @@index([username])
  @@index([user_type])
  @@index([active])
  @@index([country_id])
  @@index([city_id])
  @@index([created_at])
  @@index([user_type, lastonline_at(sort: Desc)], map: "idx_users_type_lastonline")
  @@index([user_type, hits(sort: Desc)], map: "idx_users_type_hits")
  @@index([user_type, status], map: "idx_users_type_status")
  @@map("users")
}

model Credit {
  id      Int @id @default(autoincrement())
  user_id Int @unique
  credits Int @default(0)

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

  @@map("credits")
}

model Step {
  id        Int      @id @default(autoincrement())
  user_id   Int      @unique
  step_1    Boolean  @default(false)
  step_2    Boolean  @default(false)
  step_3    Boolean  @default(false)
  step_4    Boolean  @default(false)
  step_5    Boolean  @default(false)
  step_6    Boolean  @default(false)
  completed Boolean  @default(false)
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

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

  @@map("steps")
}

// ============================================================
// GEOGRAPHIC MODELS
// ============================================================

model Continent {
  id   Int    @id @default(autoincrement())
  name String
  slug String @unique

  countries Country[]

  @@map("continents")
}

model Country {
  id           Int      @id @default(autoincrement())
  continent_id Int?
  name         String
  slug         String   @unique
  hash         String?
  a2           String?
  a3           String?
  dial_code    String?
  currency     String?
  gps_lat      Float?
  gps_long     Float?
  active       Boolean  @default(false)
  top_text     String?  @db.Text
  bottom_text  String?  @db.Text
  created_at   DateTime @default(now())
  updated_at   DateTime @updatedAt

  continent        Continent?       @relation(fields: [continent_id], references: [id])
  cities           City[]
  users            User[]
  blockedCountries BlockedCountry[]

  @@index([active])
  @@map("countries")
}

model City {
  id          Int      @id @default(autoincrement())
  country_id  Int
  name        String
  slug        String
  top_text    String?  @db.Text
  bottom_text String?  @db.Text
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt

  country Country @relation(fields: [country_id], references: [id])
  users   User[]

  @@index([country_id, slug], map: "idx_cities_country_slug")
  @@map("cities")
}

model Currency {
  id   Int    @id @default(autoincrement())
  name String
  code String?
  symbol String?

  users User[]

  @@map("currencies")
}

model Timezone {
  id   Int    @id @default(autoincrement())
  name String

  @@map("timezones")
}

model Coordinate {
  id      Int      @id @default(autoincrement())
  user_id Int      @unique
  lat     Decimal? @db.Decimal(11, 8)
  lng     Decimal? @db.Decimal(11, 8)

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

  @@map("coordinates")
}

// ============================================================
// CHARACTERISTIC MODELS
// ============================================================

model Characteristic {
  id              Int  @id @default(autoincrement())
  user_id         Int  @unique
  gender_id       Int?
  age_id          Int?
  orientation_id  Int?
  ethnicity_id    Int?
  nationality_id  Int?
  height_id       Int?
  weight_id       Int?
  eye_color_id    Int?
  hair_color_id   Int?
  hair_length_id  Int?
  hair_public_id  Int?
  breast_size_id  Int?
  breast_state_id Int?
  cup_size_id     Int?
  smoking_id      Int?
  travel_id       Int?
  calling_id      Int?
  created_at      DateTime @default(now())
  updated_at      DateTime @updatedAt

  user         User         @relation(fields: [user_id], references: [id], onDelete: Cascade)
  gender       Gender?      @relation(fields: [gender_id], references: [id])
  age          Age?         @relation(fields: [age_id], references: [id])
  orientation  Orientation? @relation(fields: [orientation_id], references: [id])
  ethnicity    Ethnicity?   @relation(fields: [ethnicity_id], references: [id])
  nationality  Nationality? @relation(fields: [nationality_id], references: [id])
  height       Height?      @relation(fields: [height_id], references: [id])
  weight       Weight?      @relation(fields: [weight_id], references: [id])
  eye_color    EyeColor?    @relation(fields: [eye_color_id], references: [id])
  hair_color   HairColor?   @relation(fields: [hair_color_id], references: [id])
  hair_length  HairLength?  @relation(fields: [hair_length_id], references: [id])
  hair_public  HairPublic?  @relation(fields: [hair_public_id], references: [id])
  breast_size  BreastSize?  @relation(fields: [breast_size_id], references: [id])
  breast_state BreastState? @relation(fields: [breast_state_id], references: [id])
  cup_size     CupSize?     @relation(fields: [cup_size_id], references: [id])
  smoking      Smoking?     @relation(fields: [smoking_id], references: [id])
  travel       Travel?      @relation(fields: [travel_id], references: [id])
  calling      Calling?     @relation(fields: [calling_id], references: [id])

  @@index([user_id])
  @@index([gender_id])
  @@index([age_id])
  @@index([orientation_id])
  @@index([ethnicity_id])
  @@index([nationality_id])
  @@index([height_id])
  @@index([weight_id])
  @@index([hair_color_id])
  @@index([eye_color_id])
  @@index([breast_size_id])
  @@index([breast_state_id])
  @@index([cup_size_id])
  @@index([smoking_id])
  @@index([gender_id, age_id])
  @@map("characteristics")
}

// Lookup tables
model Gender {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("genders")
}

model Age {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("ages")
}

model Orientation {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("orientations")
}

model Ethnicity {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("ethnicities")
}

model Nationality {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("nationalities")
}

model Height {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("heights")
}

model Weight {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("weights")
}

model EyeColor {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("eye_colors")
}

model HairColor {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("hair_colors")
}

model HairLength {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("hair_lengths")
}

model HairPublic {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("hair_publics")
}

model BreastSize {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("breast_sizes")
}

model BreastState {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("breast_states")
}

model CupSize {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("cup_sizes")
}

model Smoking {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("smokings")
}

model Travel {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("travels")
}

model Calling {
  id              Int              @id @default(autoincrement())
  name            String
  slug            String?          @unique
  characteristics Characteristic[]
  @@map("callings")
}

model Starsign {
  id   Int    @id @default(autoincrement())
  name String
  slug String? @unique
  @@map("starsigns")
}

model Language {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("languages")
}

// ============================================================
// SERVICE MODELS
// ============================================================

model Enjoy {
  id         Int         @id @default(autoincrement())
  name       String
  slug       String?     @unique
  enjoyUsers EnjoyUser[]
  @@map("enjoys")
}

model EnjoyUser {
  id        Int     @id @default(autoincrement())
  user_id   Int
  enjoy_id  Int
  cost      Decimal @default(0) @db.Decimal(8, 2)

  user  User  @relation(fields: [user_id], references: [id], onDelete: Cascade)
  enjoy Enjoy @relation(fields: [enjoy_id], references: [id], onDelete: Cascade)

  @@unique([user_id, enjoy_id])
  @@index([enjoy_id])
  @@map("enjoy_user")
}

model Rate {
  id        Int        @id @default(autoincrement())
  name      String
  slug      String?    @unique
  rateUsers RateUser[]
  ranges    Range[]
  @@map("rates")
}

model RateUser {
  id       Int @id @default(autoincrement())
  user_id  Int
  rate_id  Int
  in_call  Int?
  out_call Int?

  user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
  rate Rate @relation(fields: [rate_id], references: [id], onDelete: Cascade)

  @@unique([user_id, rate_id])
  @@map("rate_user")
}

model Range {
  id      Int @id @default(autoincrement())
  rate_id Int
  name    String?
  value   String?

  rate Rate @relation(fields: [rate_id], references: [id], onDelete: Cascade)

  @@map("ranges")
}

model Offer {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("offers")
}

model Alternative {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("alternatives")
}

model With {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("withs")
}

model Piercing {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("piercings")
}

model Tattoo {
  id    Int    @id @default(autoincrement())
  name  String
  slug  String? @unique
  users User[]
  @@map("tattoos")
}

model ContactApp {
  id   Int    @id @default(autoincrement())
  name String
  slug String? @unique
  @@map("contact_apps")
}

model Phone {
  id             Int      @id @default(autoincrement())
  user_id        Int?
  number         String?
  contact_app_id Int?
  whatsapp       Boolean  @default(false)
  telegram       Boolean  @default(false)
  telegram_id    String?
  viber          Boolean  @default(false)
  line           Boolean  @default(false)
  wechat         Boolean  @default(false)
  signalapp      Boolean  @default(false)
  created_at     DateTime @default(now())
  updated_at     DateTime @updatedAt

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

  @@index([user_id])
  @@map("phones")
}

// ============================================================
// CONTENT MODELS
// ============================================================

model Gallery {
  id          Int      @id @default(autoincrement())
  user_id     Int
  name        String
  slug        String?
  description String?  @db.Text
  credits     Int      @default(0)
  cover       String?
  private     Boolean  @default(false)
  sort_order  Int      @default(0)
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt

  user      User       @relation(fields: [user_id], references: [id], onDelete: Cascade)
  photos    Photo[]
  purchases Purchase[] @relation("gallery_purchases")

  @@index([user_id])
  @@index([private])
  @@map("galleries")
}

model Photo {
  id                Int      @id @default(autoincrement())
  user_id           Int
  gallery_id        Int?
  photo             String?
  private           Boolean  @default(false)
  aspect_ratio      Float?
  sort_order        Int      @default(0)
  type              String?
  moderation_status String   @default("approved")
  moderated_by      Int?
  moderated_at      DateTime?
  rejection_reason  String?
  created_at        DateTime @default(now())
  updated_at        DateTime @updatedAt

  user    User     @relation(fields: [user_id], references: [id], onDelete: Cascade)
  gallery Gallery? @relation(fields: [gallery_id], references: [id], onDelete: SetNull)

  @@index([user_id])
  @@index([gallery_id])
  @@index([private])
  @@index([user_id, private])
  @@index([moderation_status, created_at(sort: Desc)])
  @@map("photos")
}

model Video {
  id                Int      @id @default(autoincrement())
  user_id           Int
  name              String?
  description       String?  @db.Text
  credits           Int      @default(0)
  duration          String?
  video             String?
  aspect_ratio      Float?
  sort_order        Int      @default(0)
  private           Boolean  @default(false)
  moderation_status String   @default("approved")
  moderated_by      Int?
  moderated_at      DateTime?
  rejection_reason  String?
  created_at        DateTime @default(now())
  updated_at        DateTime @updatedAt

  user      User       @relation(fields: [user_id], references: [id], onDelete: Cascade)
  purchases Purchase[] @relation("video_purchases")

  @@index([user_id])
  @@index([private])
  @@map("videos")
}

model Blog {
  id         Int      @id @default(autoincrement())
  user_id    Int
  private    Boolean  @default(false)
  title      String?
  slug       String?
  post       String?  @db.Text
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

  user      User       @relation(fields: [user_id], references: [id], onDelete: Cascade)
  purchases Purchase[] @relation("blog_purchases")

  @@index([user_id])
  @@map("blogs")
}

model Erotica {
  id         Int      @id @default(autoincrement())
  user_id    Int
  private    Boolean  @default(false)
  title      String?
  slug       String?  @unique
  content    String?  @db.Text
  credits    Int      @default(0)
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

  user      User       @relation(fields: [user_id], references: [id], onDelete: Cascade)
  purchases Purchase[] @relation("erotica_purchases")

  @@index([user_id])
  @@map("eroticas")
}

model Feed {
  id         Int      @id @default(autoincrement())
  user_id    Int
  content    String?  @db.Text
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

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

  @@map("feeds")
}

model Faq {
  id         Int      @id @default(autoincrement())
  user_id    Int
  question   String?  @db.Text
  answer     String?  @db.Text
  sort_order Int      @default(0)
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

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

  @@map("faqs")
}

model Tag {
  id        Int        @id @default(autoincrement())
  name      String
  slug      String?    @unique
  taggables Taggable[]
  @@map("tags")
}

model Taggable {
  id            Int      @id @default(autoincrement())
  tag_id        Int
  taggable_id   Int
  taggable_type String
  created_at    DateTime @default(now())
  updated_at    DateTime @updatedAt

  tag Tag @relation(fields: [tag_id], references: [id], onDelete: Cascade)

  @@unique([tag_id, taggable_id, taggable_type])
  @@index([taggable_type, taggable_id])
  @@map("taggables")
}

// ============================================================
// COMMERCE MODELS
// ============================================================

model Purchase {
  id               Int       @id @default(autoincrement())
  user_id          Int
  purchasable_type String
  purchasable_id   Int
  price            Decimal   @db.Decimal(8, 2)
  purchased_at     DateTime?
  created_at       DateTime  @default(now())
  updated_at       DateTime  @updatedAt

  // Direct relations for common purchasable types
  gallery_id Int?
  video_id   Int?
  blog_id    Int?
  erotica_id Int?

  user    User     @relation(fields: [user_id], references: [id], onDelete: Cascade)
  gallery Gallery? @relation("gallery_purchases", fields: [gallery_id], references: [id])
  video   Video?   @relation("video_purchases", fields: [video_id], references: [id])
  blog    Blog?    @relation("blog_purchases", fields: [blog_id], references: [id])
  erotica Erotica? @relation("erotica_purchases", fields: [erotica_id], references: [id])

  @@index([user_id], map: "idx_purchases_user")
  @@index([purchasable_type, purchasable_id], map: "idx_purchases_target")
  @@map("purchases")
}

model Subscription {
  id             Int       @id @default(autoincrement())
  user_id        Int
  escort_user_id Int
  price          Decimal   @db.Decimal(8, 2)
  start_at       DateTime?
  end_at         DateTime?
  created_at     DateTime  @default(now())
  updated_at     DateTime  @updatedAt

  user   User @relation("subscriber", fields: [user_id], references: [id], onDelete: Cascade)
  escort User @relation("subscribed_escort", fields: [escort_user_id], references: [id], onDelete: Cascade)

  @@index([user_id])
  @@index([escort_user_id])
  @@map("subscriptions")
}

model EscortSubscription {
  id       Int     @id @default(autoincrement())
  user_id  Int     @unique
  price    Decimal @default(0) @db.Decimal(8, 2)
  active   Boolean @default(true)

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

  @@map("escort_subscriptions")
}

model Transaction {
  id                   Int             @id @default(autoincrement())
  user_id              Int
  receiver_id          Int?
  transactionable_id   Int?
  transactionable_type String?
  amount               Decimal         @db.Decimal(8, 2)
  type                 TransactionType
  transaction_date     DateTime?
  created_at           DateTime        @default(now())
  updated_at           DateTime        @updatedAt

  user     User  @relation("transaction_sender", fields: [user_id], references: [id], onDelete: Cascade)
  receiver User? @relation("transaction_receiver", fields: [receiver_id], references: [id])

  @@index([user_id])
  @@index([type])
  @@index([receiver_id, created_at(sort: Desc)])
  @@map("transactions")
}

// ============================================================
// SOCIAL MODELS
// ============================================================

model Review {
  id          Int      @id @default(autoincrement())
  author      String?
  user_id     Int
  date        DateTime?
  location    String?
  stars       Int?
  length      String?
  costs       String?
  review_text String?  @db.Text
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt

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

  @@index([user_id])
  @@map("reviews")
}

model Report {
  id              Int      @id @default(autoincrement())
  user_id         Int
  reportable_type String
  reportable_id   Int
  reason          String?  @db.Text
  created_at      DateTime @default(now())
  updated_at      DateTime @updatedAt

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

  @@index([user_id])
  @@index([reportable_type, reportable_id])
  @@map("reports")
}

model Notification {
  id         Int      @id @default(autoincrement())
  user_id    Int
  content    String?  @db.Text
  link       String?  @db.Text
  is_read    Boolean  @default(false)
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

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

  @@index([user_id, is_read], map: "idx_notifications_user_unread")
  @@map("notifications")
}

model Newsletter {
  id                Int       @id @default(autoincrement())
  email             String    @unique
  confirm_token     String?   @unique
  unsubscribe_token String?   @unique
  confirmed_at      DateTime?
  unsubscribed_at   DateTime?
  created_at        DateTime  @default(now())

  @@map("newsletters")
}

model Favorite {
  id            Int      @id @default(autoincrement())
  user_id       Int
  markable_type String
  markable_id   Int
  created_at    DateTime @default(now())

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

  @@index([user_id])
  @@index([markable_type, markable_id])
  @@map("favorites")
}

model LivecamRequest {
  id                Int      @id @default(autoincrement())
  requester_user_id Int
  escort_user_id    Int
  requested_at      DateTime @db.Timestamptz()
  message           String?  @db.Text
  status            String   @default("pending") @db.VarChar(20)
  created_at        DateTime @default(now()) @db.Timestamptz()
  updated_at        DateTime @default(now()) @updatedAt @db.Timestamptz()

  @@index([escort_user_id, status], map: "idx_livecam_requests_escort")
  @@index([requester_user_id], map: "idx_livecam_requests_requester")
  @@map("livecam_requests")
}

model PollVote {
  overlay_id   Int
  user_id      Int
  option_index Int
  voted_at     DateTime @default(now()) @db.Timestamptz()

  @@id([overlay_id, user_id])
  @@map("poll_votes")
}

model PushSubscription {
  id           Int      @id @default(autoincrement())
  user_id      Int
  endpoint     String   @unique @db.Text
  p256dh       String   @db.Text
  auth         String   @db.Text
  user_agent   String?  @db.Text
  created_at   DateTime @default(now()) @db.Timestamptz()
  last_used_at DateTime @default(now()) @db.Timestamptz()

  @@index([user_id], map: "idx_push_subscriptions_user")
  @@map("push_subscriptions")
}

model Hit {
  id         Int      @id @default(autoincrement())
  user_id    Int
  // R15 A.7: column was added in production via Mgmt-API DDL (R14 D.x) but
  // the schema was never updated; ORM callers got TypeScript errors. The
  // analytics chart already reads it via raw SQL.
  created_at DateTime @default(now()) @db.Timestamptz(6)

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

  @@index([user_id, created_at(sort: Desc)])
  @@map("hits")
}

model Search {
  id           String   @id @default(uuid())
  user_id      Int?
  name         String?
  query_string String?  @db.Text
  created_at   DateTime @default(now())
  updated_at   DateTime @updatedAt

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

  @@index([user_id])
  @@map("searches")
}

// ============================================================
// MESSAGING MODELS
// ============================================================

model Room {
  id         Int       @id @default(autoincrement())
  stream_id  Int?      @unique
  user_id    Int?
  created_at DateTime  @default(now())
  updated_at DateTime  @updatedAt

  user     User?          @relation(fields: [user_id], references: [id])
  stream   WowzaStream?   @relation(fields: [stream_id], references: [id])
  messages Message[]

  @@map("rooms")
}

model Message {
  id             Int       @id @default(autoincrement())
  room_id        Int
  user_id        Int
  body           String?   @db.Text
  is_whisper     Boolean   @default(false)
  read_at        DateTime? @db.Timestamptz(6)
  attachment_url String?
  created_at     DateTime  @default(now())
  updated_at     DateTime  @updatedAt

  room Room @relation(fields: [room_id], references: [id], onDelete: Cascade)
  user User @relation(fields: [user_id], references: [id], onDelete: Cascade)

  @@index([created_at(sort: Desc)])
  @@index([room_id, created_at(sort: Desc)])
  @@map("messages")
}

// ============================================================
// STREAMING MODELS
// ============================================================

model WowzaStream {
  id                Int      @id @default(autoincrement())
  user_id           Int
  wowza_id          String?
  wowza_sdp         String?
  wowza_app         String?
  wowza_stream_name String?
  active            Boolean  @default(false)
  started           Boolean  @default(false)
  streaming         Boolean  @default(false)
  created_at        DateTime @default(now())
  updated_at        DateTime @updatedAt

  user User   @relation(fields: [user_id], references: [id], onDelete: Cascade)
  room Room?

  @@map("wowza_streams")
}

// ============================================================
// SCHEDULE MODELS
// ============================================================

model Availability {
  id         Int      @id @default(autoincrement())
  user_id    Int?
  day        String
  start_at   String?
  end_at     String?
  is_all_day Boolean  @default(false)
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

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

  @@index([user_id])
  @@map("availabilities")
}

// ============================================================
// MISC MODELS
// ============================================================

model Count {
  id             Int    @id @default(autoincrement())
  countable_type String
  countable_id   Int
  count          Int    @default(0)

  @@map("counts")
}

model Statistic {
  id            Int    @id @default(autoincrement())
  resource_type String
  resource_id   Int
  value         Int    @default(0)

  @@map("statistics")
}

model Website {
  id   Int    @id @default(autoincrement())
  key  String @unique
  value String? @db.Text

  @@map("websites")
}

model PasswordReset {
  id         Int      @id @default(autoincrement())
  // R15 A.8: email is unique — forgot-password upserts on email and the
  // route formerly relied on this constraint at runtime. Mirrored in DB
  // via Mgmt-API ALTER TABLE.
  email      String   @unique
  token      String
  created_at DateTime @default(now())

  @@map("password_resets")
}

model Session {
  id            Int      @id @default(autoincrement())
  user_id       Int?
  ip_address    String?
  user_agent    String?  @db.Text
  payload       String?  @db.Text
  last_activity Int?

  @@map("sessions")
}

// ============================================================
// BLOG CONTENT MODELS
// ============================================================

model BlogPost {
  id               Int       @id @default(autoincrement())
  title            String
  slug             String    @unique
  excerpt          String?   @db.Text
  content          String    @db.Text
  meta_title       String?
  meta_description String?   @db.Text
  og_image         String?
  category         String?
  tags             String?
  read_time        Int       @default(5)
  views            Int       @default(0)
  featured         Boolean   @default(false)
  published        Boolean   @default(true)
  published_at     DateTime?
  author_id        Int?
  created_at       DateTime  @default(now())
  updated_at       DateTime  @updatedAt

  @@index([published])
  @@index([slug])
  @@index([category])
  @@index([published, published_at])
  @@index([featured])
  @@map("blog_posts")
}

// ============================================================
// ADMIN & MODERATION MODELS
// ============================================================

model AuditLog {
  id          Int      @id @default(autoincrement())
  admin_id    Int
  action      String
  target_type String
  target_id   Int
  details     String?  @db.Text
  ip_address  String?
  created_at  DateTime @default(now())

  @@index([admin_id])
  @@index([target_type, target_id])
  @@index([created_at])
  @@map("audit_logs")
}

model HelpArticle {
  id               Int      @id @default(autoincrement())
  title            String
  slug             String   @unique
  excerpt          String?  @db.Text
  content          String   @db.Text
  category         String
  tags             String?
  sort_order       Int      @default(0)
  views            Int      @default(0)
  helpful_yes      Int      @default(0)
  helpful_no       Int      @default(0)
  published        Boolean  @default(true)
  created_at       DateTime @default(now())
  updated_at       DateTime @updatedAt

  @@index([slug])
  @@index([category])
  @@index([published])
  @@map("help_articles")
}

// ============================================================
// SUPPORT TICKET MODELS
// ============================================================

model Ticket {
  id          Int             @id @default(autoincrement())
  user_id     Int
  subject     String
  category    String
  form_type   String?
  status      String          @default("open")
  priority    String          @default("normal")
  assigned_to Int?
  ai_summary  String?         @db.Text
  resolved_at DateTime?
  created_at  DateTime        @default(now())
  updated_at  DateTime        @updatedAt

  messages TicketMessage[]

  @@index([user_id])
  @@index([status])
  @@index([category])
  @@index([assigned_to])
  @@index([created_at])
  @@map("tickets")
}

model TicketMessage {
  id          Int      @id @default(autoincrement())
  ticket_id   Int
  user_id     Int?
  body        String   @db.Text
  is_admin    Boolean  @default(false)
  is_ai       Boolean  @default(false)
  attachments String?  @db.Text
  created_at  DateTime @default(now())

  ticket Ticket @relation(fields: [ticket_id], references: [id], onDelete: Cascade)

  @@index([ticket_id])
  @@map("ticket_messages")
}

// ============================================================
// PAYOUT, BOOST, BAN & ANNOUNCEMENT MODELS
// ============================================================

model PayoutRequest {
  id          Int       @id @default(autoincrement())
  user_id     Int
  amount      Decimal   @db.Decimal(10, 2)
  method      String    @default("bank_transfer")
  status      String    @default("pending") // pending, processing, completed, rejected
  notes       String?   @db.Text
  admin_notes String?   @db.Text
  processed_by Int?
  processed_at DateTime?
  created_at  DateTime  @default(now())
  updated_at  DateTime  @updatedAt

  @@index([user_id])
  @@index([status])
  @@index([created_at])
  @@map("payout_requests")
}

model FeaturedPackage {
  id          Int      @id @default(autoincrement())
  name        String
  slug        String   @unique
  description String?  @db.Text
  price       Int      @default(0)
  duration_days Int    @default(1)
  type        String   // "available_today", "featured", "homepage_spotlight", "search_boost"
  active      Boolean  @default(true)
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt

  @@map("featured_packages")
}

model FeaturedPurchase {
  id          Int       @id @default(autoincrement())
  user_id     Int
  package_id  Int
  starts_at   DateTime  @default(now())
  expires_at  DateTime
  active      Boolean   @default(true)
  created_at  DateTime  @default(now())

  @@index([user_id])
  @@index([active, expires_at])
  @@map("featured_purchases")
}

model IpBan {
  id         Int      @id @default(autoincrement())
  ip_address String
  reason     String?
  banned_by  Int?
  expires_at DateTime?
  created_at DateTime @default(now())

  @@index([ip_address])
  @@map("ip_bans")
}

model Announcement {
  id         Int       @id @default(autoincrement())
  title      String
  content    String    @db.Text
  type       String    @default("info") // info, warning, success, urgent
  target     String    @default("all")  // all, escorts, clients, agencies
  active     Boolean   @default(true)
  starts_at  DateTime  @default(now())
  expires_at DateTime?
  created_at DateTime  @default(now())
  updated_at DateTime  @updatedAt

  @@index([active, starts_at])
  @@map("announcements")
}

// ============================================================
// SUBSCRIPTION & REFERRAL MODELS
// ============================================================

model SubscriptionPlan {
  id                  Int      @id @default(autoincrement())
  name                String
  slug                String   @unique
  description         String?  @db.Text
  price_monthly       Int      @default(0)
  price_yearly        Int      @default(0)
  features            String?  @db.Text
  max_photos          Int      @default(10)
  max_galleries       Int      @default(3)
  max_videos          Int      @default(0)
  analytics_access    Boolean  @default(false)
  priority_search     Boolean  @default(false)
  verified_badge      Boolean  @default(false)
  vip_badge           Boolean  @default(false)
  unlimited_messages  Boolean  @default(false)
  active              Boolean  @default(true)
  created_at          DateTime @default(now())
  updated_at          DateTime @updatedAt

  @@map("subscription_plans")
}

model UserSubscription {
  id                     Int       @id @default(autoincrement())
  user_id                Int
  plan_id                Int
  // R15 A.8: stripe id is unique — webhook upserts ON CONFLICT on this
  // column. Constraint added via Mgmt-API ALTER TABLE; mirrored here so
  // future regenerations don't lose it.
  stripe_subscription_id String?   @unique
  status                 String    @default("active")
  current_period_start   DateTime?
  current_period_end     DateTime?
  cancel_at_period_end   Boolean   @default(false)
  created_at             DateTime  @default(now())
  updated_at             DateTime  @updatedAt

  @@index([user_id])
  @@index([status])
  @@map("user_subscriptions")
}

model Referral {
  id             Int       @id @default(autoincrement())
  referrer_id    Int
  referred_id    Int?
  code           String    @unique
  status         String    @default("pending")
  reward_credits Int       @default(10)
  claimed_at     DateTime?
  created_at     DateTime  @default(now())

  @@index([referrer_id])
  @@index([code])
  @@map("referrals")
}

// ============================================================
// AGE VERIFICATION (UK Online Safety Act)
// ============================================================

model AgeVerificationLog {
  id         Int      @id @default(autoincrement())
  user_id    Int?
  ip_address String?
  country    String?
  method     String
  status     String   @default("pending")
  session_id String?
  details    String?  @db.Text
  created_at DateTime @default(now())

  @@index([user_id])
  @@index([ip_address])
  @@index([status])
  @@map("age_verification_logs")
}

// ============================================================
// LIVECAM / DIRECTCAM MODELS
// ============================================================

model LiveCamSettings {
  id               Int      @id @default(autoincrement())
  user_id          Int      @unique
  free_max_minutes Int      @default(3)
  group_rate       Int      @default(5)
  private_rate     Int      @default(10)
  voyeur_rate      Int      @default(3)
  audio_enabled    Boolean  @default(true)
  category         String?
  strapline        String?
  is_live          Boolean  @default(false)
  created_at       DateTime @default(now())
  updated_at       DateTime @updatedAt

  @@map("live_cam_settings")
}

model LiveSession {
  id            Int       @id @default(autoincrement())
  provider_id   Int
  stream_id     Int?
  mode          String    @default("free")
  started_at    DateTime  @default(now())
  ended_at      DateTime?
  total_credits Int       @default(0)
  viewer_count  Int       @default(0)

  @@index([provider_id])
  @@index([mode])
  @@map("live_sessions")
}

model LiveSessionViewer {
  id            Int       @id @default(autoincrement())
  session_id    Int
  user_id       Int
  mode          String    @default("free")
  joined_at     DateTime  @default(now())
  left_at       DateTime?
  credits_spent Int       @default(0)
  is_kicked     Boolean   @default(false)

  @@index([session_id])
  @@index([user_id])
  @@map("live_session_viewers")
}

// ============================================================
// R15 A.7: schema reconciliation — tables added in production via Mgmt-API
// DDL across rounds 8-14 but never mirrored to the schema. ORM callers had
// to use raw SQL; adding them here unlocks the typed Prisma client.
// ============================================================

model BookingRequest {
  id                          Int       @id @default(autoincrement())
  client_id                   Int
  escort_id                   Int
  // requested_date is a `date` column in pg, not timestamp. The reschedule
  // proposal column is timestamptz so we can capture an exact new time.
  requested_date              DateTime  @db.Date
  duration                    String
  type                        String    @default("incall")
  status                      String    @default("pending")
  notes                       String?   @db.Text
  total_price                 Int?
  reschedule_requested_date   DateTime? @db.Timestamptz(6)
  reminder_sent_at            DateTime? @db.Timestamptz(6)
  created_at                  DateTime  @default(now()) @db.Timestamptz(6)
  updated_at                  DateTime  @updatedAt

  @@index([client_id, created_at(sort: Desc)])
  @@index([escort_id, created_at(sort: Desc)])
  @@index([status])
  @@map("booking_requests")
}

model PanicAlert {
  id         Int      @id @default(autoincrement())
  user_id    Int
  latitude   Float?
  longitude  Float?
  ip_address String?  @db.VarChar(45)
  // pg column is `timestamp without time zone`.
  created_at DateTime @default(now()) @db.Timestamp(6)

  @@index([user_id])
  @@map("panic_alerts")
}

model RecentlyViewed {
  id             Int      @id @default(autoincrement())
  user_id        Int
  viewed_user_id Int
  viewed_at      DateTime @default(now()) @db.Timestamptz(6)

  @@unique([user_id, viewed_user_id], map: "uq_recently_viewed_user_target")
  @@index([viewed_user_id, viewed_at(sort: Desc)])
  @@map("recently_viewed")
}

model MessagingSettings {
  id                 Int      @id @default(autoincrement())
  user_id            Int      @unique
  per_message_price  Decimal? @db.Decimal(10, 2)
  enabled            Boolean  @default(true)
  created_at         DateTime @default(now()) @db.Timestamptz(6)
  updated_at         DateTime @updatedAt

  @@map("messaging_settings")
}

model BlockedUser {
  id         Int      @id @default(autoincrement())
  blocker_id Int
  blocked_id Int
  reason     String?  @db.Text
  created_at DateTime @default(now()) @db.Timestamptz(6)

  @@unique([blocker_id, blocked_id])
  @@index([blocked_id])
  @@map("blocked_users")
}

model BlockedCountry {
  id                 Int @id @default(autoincrement())
  blocker_user_id    Int
  blocked_country_id Int

  user    User    @relation(fields: [blocker_user_id], references: [id], onDelete: Cascade)
  country Country @relation(fields: [blocked_country_id], references: [id], onDelete: Cascade)

  @@unique([blocker_user_id, blocked_country_id])
  @@index([blocker_user_id])
  @@map("blacklist_country")
}

model SafetySettings {
  id            Int      @id @default(autoincrement())
  user_id       Int
  contact_type  String   @default("phone") @db.VarChar(10)
  contact_value String   @default("") @db.VarChar(255)
  panic_enabled Boolean  @default(true)
  created_at    DateTime @default(now()) @db.Timestamp(6)
  updated_at    DateTime @default(now()) @db.Timestamp(6)

  @@index([user_id])
  @@map("safety_settings")
}

model StripeProcessedEvent {
  // event_id is the primary key; webhook idempotency relies on the natural
  // unique-key behaviour. Constraint already in place in production.
  event_id     String   @id
  type         String?
  processed_at DateTime @default(now()) @db.Timestamptz(6)

  @@map("stripe_processed_events")
}

// R15 E.8: per-user canned replies for chat. Surfaced via 💬 picker in
// the chat input bar; CRUD lives at /manage/messaging/replies.
model SavedReply {
  id         Int      @id @default(autoincrement())
  user_id    Int
  label      String   @db.VarChar(64)
  body       String   @db.Text
  sort_order Int      @default(0)
  created_at DateTime @default(now()) @db.Timestamptz(6)
  updated_at DateTime @updatedAt

  @@index([user_id, sort_order])
  @@map("saved_replies")
}

// R18 D.2: schema sync. Both tables exist in production via raw-SQL DDL
// but were never mirrored back into the schema, so Prisma client had no
// types and any future regeneration could lose them.

model ConsentLog {
  id           Int      @id @default(autoincrement())
  user_id      Int?
  type         String   @db.VarChar(64)
  ip_address   String?  @db.VarChar(45)
  user_agent   String?  @db.Text
  consented_at DateTime @default(now()) @db.Timestamp(6)

  @@index([user_id, consented_at(sort: Desc)])
  @@map("consent_logs")
}

model EmailJob {
  id         BigInt   @id @default(autoincrement())
  kind       String
  recipient  String
  payload    Json?
  status     String   @default("failed")
  attempts   Int      @default(0)
  last_error String?  @db.Text
  created_at DateTime @default(now()) @db.Timestamptz(6)
  updated_at DateTime @updatedAt

  @@index([status, created_at(sort: Desc)])
  @@map("email_jobs")
}

// R15 D.9: aggregated profile-view counts. Replaces the unbounded `hits`
// table — cardinality drops from O(events) to O(users * days). The
// analytics chart now reads from this table; new view events INSERT ...
// ON CONFLICT DO UPDATE on (user_id, date).
model DailyHit {
  user_id Int
  date    DateTime @db.Date
  count   Int      @default(0)

  @@id([user_id, date])
  @@index([user_id, date(sort: Desc)])
  @@map("daily_hits")
}

// R15 B.1 (preempt): TOTP recovery codes — bcrypt-hashed one-time codes
// generated at 2FA enrolment. Allow `/api/auth/2fa/disable` to consume one
// in lieu of a TOTP code when the user has lost their authenticator.
model TotpRecoveryCode {
  id         Int       @id @default(autoincrement())
  user_id    Int
  code_hash  String
  used_at    DateTime? @db.Timestamptz(6)
  created_at DateTime  @default(now()) @db.Timestamptz(6)

  @@index([user_id])
  @@map("totp_recovery_codes")
}

// Spatie laravel-medialibrary table, carried over from the legacy Laravel
// app. This is the source of truth for every R2 object path: originals live
// at `{id}/{file_name}`, conversions at `{id}/conversions/{stem}-{name}.ext`
// (see generated_conversions for which conversion names actually exist).
// model_id is the legacy numeric `users.id` — model_type is always
// 'App\Models\User' in this dataset.
model Media {
  id                    Int      @id
  model_type            String
  model_id              Int
  uuid                  String?
  collection_name       String
  name                  String
  file_name             String
  mime_type             String?
  disk                  String
  conversions_disk      String?
  size                  Int
  manipulations         Json
  custom_properties     Json
  generated_conversions Json
  responsive_images     Json
  order_column          Int?
  created_at            DateTime?
  updated_at            DateTime?

  @@index([model_type, model_id, collection_name])
  @@map("media")
}
