// This is your Prisma schema file

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

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

generator json {
  provider = "prisma-json-types-generator"
}

enum CommentStatus {
  PENDING
  HUMAN_PENDING
  APPROVED
  VERIFIED
  REJECTED
}

enum OrderIdStatus {
  PENDING
  APPROVED
  REJECTED
  WITHDRAWN
}

model Comment {
  id                  Int                @id @default(autoincrement())
  /// Computed via trigger. Do not update through prisma.
  upvotes             Int                @default(0)
  status              CommentStatus      @default(PENDING)
  suspicious          Boolean            @default(false)
  requiresAdminReview Boolean            @default(false)
  communityNote       String?
  verificationNote    String?
  internalNote        String?
  privateContext      String?
  orderId             String?            @db.VarChar(100)
  orderIdStatus       OrderIdStatus?     @default(PENDING)
  kycRequested        Boolean            @default(false)
  fundsBlocked        Boolean            @default(false)
  content             String
  rating              Int?               @db.SmallInt
  ratingActive        Boolean            @default(false)
  createdAt           DateTime           @default(now())
  updatedAt           DateTime           @default(now()) @updatedAt
  author              User               @relation(fields: [authorId], references: [id], onDelete: Cascade)
  authorId            Int
  service             Service            @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId           Int
  parentId            Int?
  parent              Comment?           @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
  replies             Comment[]          @relation("CommentReplies")
  karmaTransactions   KarmaTransaction[]
  votes               CommentVote[]

  notificationPreferenceswatchedComments NotificationPreferences[] @relation("watchedComments")
  Notification                           Notification[]

  @@unique([serviceId, orderId], name: "unique_orderId_per_service")
  @@index([status])
  @@index([createdAt])
  @@index([serviceId])
  @@index([authorId])
  @@index([upvotes])
  @@index([rating])
  @@index([ratingActive])
}

enum VerificationStatus {
  COMMUNITY_CONTRIBUTED
  // COMMUNITY_VERIFIED
  APPROVED
  VERIFICATION_SUCCESS
  VERIFICATION_FAILED
}

enum ServiceInfoBanner {
  NONE
  NO_LONGER_OPERATIONAL
}

enum ServiceVisibility {
  PUBLIC
  UNLISTED
  HIDDEN
  ARCHIVED
}

enum Currency {
  MONERO
  BITCOIN
  LIGHTNING
  FIAT
  CASH
}

enum EventType {
  WARNING
  WARNING_SOLVED
  ALERT
  ALERT_SOLVED
  INFO
  NORMAL
  UPDATE
}

enum ServiceUserRole {
  OWNER
  ADMIN
  MODERATOR
  SUPPORT
  TEAM_MEMBER
}

enum AccountStatusChange {
  ADMIN_TRUE
  ADMIN_FALSE
  VERIFIED_TRUE
  VERIFIED_FALSE
  MODERATOR_TRUE
  MODERATOR_FALSE
  SPAMMER_TRUE
  SPAMMER_FALSE
}

enum NotificationType {
  TEST
  COMMENT_STATUS_CHANGE
  REPLY_COMMENT_CREATED
  COMMUNITY_NOTE_ADDED
  /// Comment that is not a reply. May include a rating.
  ROOT_COMMENT_CREATED
  SUGGESTION_CREATED
  SUGGESTION_MESSAGE
  SUGGESTION_STATUS_CHANGE
  // KARMA_UNLOCK // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code.
  KARMA_CHANGE
  /// Marked as spammer, promoted to admin, etc.
  ACCOUNT_STATUS_CHANGE
  EVENT_CREATED
  SERVICE_VERIFICATION_STATUS_CHANGE
}

enum CommentStatusChange {
  MARKED_AS_SPAM
  UNMARKED_AS_SPAM
  MARKED_FOR_ADMIN_REVIEW
  UNMARKED_FOR_ADMIN_REVIEW
  STATUS_CHANGED_TO_APPROVED
  STATUS_CHANGED_TO_VERIFIED
  STATUS_CHANGED_TO_REJECTED
  STATUS_CHANGED_TO_PENDING
}

enum ServiceVerificationStatusChange {
  STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED
  STATUS_CHANGED_TO_APPROVED
  STATUS_CHANGED_TO_VERIFICATION_SUCCESS
  STATUS_CHANGED_TO_VERIFICATION_FAILED
}

enum ServiceSuggestionStatusChange {
  STATUS_CHANGED_TO_PENDING
  STATUS_CHANGED_TO_APPROVED
  STATUS_CHANGED_TO_REJECTED
  STATUS_CHANGED_TO_WITHDRAWN
}

enum KarmaTransactionAction {
  COMMENT_APPROVED
  COMMENT_VERIFIED
  COMMENT_SPAM
  COMMENT_SPAM_REVERTED
  COMMENT_UPVOTE
  COMMENT_DOWNVOTE
  COMMENT_VOTE_REMOVED
  SUGGESTION_APPROVED
  MANUAL_ADJUSTMENT
}

enum AnnouncementType {
  INFO
  WARNING
  ALERT
}

model Notification {
  id        Int              @id @default(autoincrement())
  userId    Int
  user      User             @relation("NotificationOwner", fields: [userId], references: [id], onDelete: Cascade)
  type      NotificationType
  read      Boolean          @default(false)
  createdAt DateTime         @default(now())
  updatedAt DateTime         @default(now()) @updatedAt

  aboutComment                         Comment?                         @relation(fields: [aboutCommentId], references: [id])
  aboutCommentId                       Int?
  aboutEvent                           Event?                           @relation(fields: [aboutEventId], references: [id])
  aboutEventId                         Int?
  aboutService                         Service?                         @relation(fields: [aboutServiceId], references: [id])
  aboutServiceId                       Int?
  aboutServiceSuggestion               ServiceSuggestion?               @relation(fields: [aboutServiceSuggestionId], references: [id])
  aboutServiceSuggestionId             Int?
  aboutServiceSuggestionMessage        ServiceSuggestionMessage?        @relation(fields: [aboutServiceSuggestionMessageId], references: [id])
  aboutServiceSuggestionMessageId      Int?
  aboutAccountStatusChange             AccountStatusChange?
  aboutCommentStatusChange             CommentStatusChange?
  aboutServiceVerificationStatusChange ServiceVerificationStatusChange?
  aboutSuggestionStatusChange          ServiceSuggestionStatusChange?
  aboutKarmaTransaction                KarmaTransaction?                @relation(fields: [aboutKarmaTransactionId], references: [id])
  aboutKarmaTransactionId              Int?

  @@index([userId])
  @@index([read])
  @@index([createdAt])
  @@index([userId, read, createdAt])
  @@index([userId, type, aboutCommentId])
  @@index([userId, type, aboutServiceSuggestionMessageId], map: "idx_notification_suggestion_message")
  @@index([userId, type, aboutServiceSuggestionId], map: "idx_notification_suggestion_status")
  @@index([userId, type, aboutAccountStatusChange], map: "idx_notification_account_status")
}

model NotificationPreferences {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

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

  enableOnMyCommentStatusChange     Boolean @default(true)
  enableAutowatchMyComments         Boolean @default(true)
  enableNotifyPendingRepliesOnWatch Boolean @default(false)
  karmaNotificationThreshold        Int     @default(10)

  onEventCreatedForServices       Service[] @relation("onEventCreatedForServices")
  onRootCommentCreatedForServices Service[] @relation("onRootCommentCreatedForServices")
  onVerificationChangeForServices Service[] @relation("onVerificationChangeForServices")
  watchedComments                 Comment[] @relation("watchedComments")

  onServiceVerificationChangeFilter NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
}

model NotificationPreferenceOnServiceVerificationChangeFilterFilter {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

  verificationStatus        VerificationStepStatus
  notificationPreferences   NotificationPreferences @relation(fields: [notificationPreferencesId], references: [id], onDelete: Cascade)
  notificationPreferencesId Int

  categories Category[]
  attributes Attribute[]
  currencies Currency[]
  /// 0-10
  scores     Int[]

  @@unique([verificationStatus, notificationPreferencesId])
}

model Event {
  id           Int            @id @default(autoincrement())
  title        String
  content      String
  source       String?
  type         EventType
  visible      Boolean        @default(true)
  startedAt    DateTime
  /// If null, the event is ongoing. If same as startedAt, the event is a one-time event. If startedAt is in the future, the event is upcoming.
  endedAt      DateTime?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @default(now()) @updatedAt
  service      Service        @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId    Int
  Notification Notification[]

  @@index([visible])
  @@index([startedAt])
  @@index([createdAt])
  @@index([endedAt])
  @@index([type])
  @@index([serviceId])
}

enum ServiceSuggestionStatus {
  PENDING
  APPROVED
  REJECTED
  WITHDRAWN
}

enum ServiceSuggestionType {
  CREATE_SERVICE
  EDIT_SERVICE
}

enum KycLevelClarification {
  NONE
  DEPENDS_ON_PARTNERS
}

model ServiceSuggestion {
  id               Int                        @id @default(autoincrement())
  type             ServiceSuggestionType
  status           ServiceSuggestionStatus    @default(PENDING)
  notes            String?
  createdAt        DateTime                   @default(now())
  updatedAt        DateTime                   @default(now()) @updatedAt
  userId           Int
  serviceId        Int
  user             User                       @relation(fields: [userId], references: [id], onDelete: Cascade)
  service          Service                    @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  messages         ServiceSuggestionMessage[]
  Notification     Notification[]
  KarmaTransaction KarmaTransaction[]

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

model ServiceSuggestionMessage {
  id            Int               @id @default(autoincrement())
  content       String
  createdAt     DateTime          @default(now())
  updatedAt     DateTime          @default(now()) @updatedAt
  userId        Int
  suggestionId  Int
  user          User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  suggestion    ServiceSuggestion @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
  notifications Notification[]

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

model Service {
  id                     Int                          @id @default(autoincrement())
  name                   String
  slug                   String                       @unique
  previousSlugs          String[]                     @default([])
  description            String
  categories             Category[]                   @relation("ServiceToCategory")
  kycLevel               Int                          @default(4)
  kycLevelClarification  KycLevelClarification        @default(NONE)
  overallScore           Int                          @default(0)
  privacyScore           Int                          @default(0)
  trustScore             Int                          @default(0)
  /// Computed via trigger. Do not update through prisma.
  averageUserRating      Float?
  serviceVisibility      ServiceVisibility            @default(PUBLIC)
  serviceInfoBanner      ServiceInfoBanner            @default(NONE)
  serviceInfoBannerNotes String?
  verificationStatus     VerificationStatus           @default(COMMUNITY_CONTRIBUTED)
  verificationSummary    String?
  verificationRequests   ServiceVerificationRequest[]
  verificationProofMd    String?
  /// [UserSentiment]
  userSentiment          Json?
  userSentimentAt        DateTime?
  referral               String?
  acceptedCurrencies     Currency[]                   @default([])
  serviceUrls            String[]
  tosUrls                String[]                     @default([])
  onionUrls              String[]                     @default([])
  i2pUrls                String[]                     @default([])
  imageUrl               String?
  /// [TosReview]
  tosReview              Json?
  tosReviewAt            DateTime?
  createdAt              DateTime                     @default(now())
  updatedAt              DateTime                     @default(now()) @updatedAt
  /// Computed via trigger when the visibility is PUBLIC or (ARCHIVED and listedAt was null). Do not update through prisma.
  listedAt               DateTime?
  /// Computed via trigger when the verification status is APPROVED. Do not update through prisma.
  approvedAt             DateTime?
  /// Computed via trigger when the verification status is VERIFICATION_SUCCESS. Do not update through prisma.
  verifiedAt             DateTime?
  /// Computed via trigger when the verification status is VERIFICATION_FAILED. Do not update through prisma.
  spamAt                 DateTime?
  /// Computed via trigger. Do not update through prisma.
  isRecentlyApproved     Boolean                      @default(false)
  comments               Comment[]
  events                 Event[]
  contactMethods         ServiceContactMethod[]       @relation("ServiceToContactMethod")
  attributes             ServiceAttribute[]
  verificationSteps      VerificationStep[]
  suggestions            ServiceSuggestion[]
  internalNotes          InternalServiceNote[]        @relation("ServiceRecievedNotes")

  onEventCreatedForServices       NotificationPreferences[] @relation("onEventCreatedForServices")
  onRootCommentCreatedForServices NotificationPreferences[] @relation("onRootCommentCreatedForServices")
  onVerificationChangeForServices NotificationPreferences[] @relation("onVerificationChangeForServices")
  Notification                    Notification[]
  affiliatedUsers                 ServiceUser[]             @relation("ServiceUsers")

  @@index([listedAt])
  @@index([approvedAt])
  @@index([verifiedAt])
  @@index([spamAt])
  @@index([overallScore])
  @@index([privacyScore])
  @@index([trustScore])
  @@index([averageUserRating])
  @@index([name])
  @@index([verificationStatus])
  @@index([kycLevel])
  @@index([createdAt])
  @@index([updatedAt])
  @@index([slug])
  @@index([previousSlugs])
  @@index([serviceVisibility])
}

model ServiceContactMethod {
  id    Int     @id @default(autoincrement())
  /// Only include it if you want to override the formatted value.
  label String?
  /// Including the protocol (e.g. "mailto:", "tel:", "https://")
  value String

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

  services  Service @relation("ServiceToContactMethod", fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId Int
}

enum AttributeCategory {
  PRIVACY
  TRUST
}

enum AttributeType {
  GOOD
  BAD
  WARNING
  INFO
}

model Attribute {
  id            Int                @id @default(autoincrement())
  slug          String             @unique
  title         String
  /// Markdown
  description   String
  privacyPoints Int                @default(0)
  trustPoints   Int                @default(0)
  category      AttributeCategory
  type          AttributeType
  createdAt     DateTime           @default(now())
  updatedAt     DateTime           @default(now()) @updatedAt
  services      ServiceAttribute[]

  notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
}

model InternalUserNote {
  id        Int      @id @default(autoincrement())
  /// Markdown
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

  user          User  @relation("UserRecievedNotes", fields: [userId], references: [id], onDelete: Cascade)
  userId        Int
  addedByUser   User? @relation("UserAddedNotes", fields: [addedByUserId], references: [id], onDelete: SetNull)
  addedByUserId Int?

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

model InternalServiceNote {
  id        Int      @id @default(autoincrement())
  /// Markdown
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

  service       Service @relation("ServiceRecievedNotes", fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId     Int
  addedByUser   User?   @relation("UserAddedServiceNotes", fields: [addedByUserId], references: [id], onDelete: SetNull)
  addedByUserId Int?

  @@index([serviceId])
  @@index([addedByUserId])
  @@index([createdAt])
}

model User {
  id              Int     @id @default(autoincrement())
  name            String  @unique
  displayName     String?
  link            String?
  picture         String?
  spammer         Boolean @default(false)
  verified        Boolean @default(false)
  admin           Boolean @default(false)
  moderator       Boolean @default(false)
  verifiedLink    String?
  secretTokenHash String  @unique
  feedId          String  @unique @default(cuid(2))
  /// Computed via trigger. Do not update through prisma.
  totalKarma      Int     @default(0)

  createdAt                DateTime                     @default(now())
  updatedAt                DateTime                     @default(now()) @updatedAt
  lastLoginAt              DateTime                     @default(now())
  comments                 Comment[]
  karmaTransactions        KarmaTransaction[]
  grantedKarmaTransactions KarmaTransaction[]           @relation("KarmaGrantedBy")
  commentVotes             CommentVote[]
  suggestions              ServiceSuggestion[]
  suggestionMessages       ServiceSuggestionMessage[]
  internalNotes            InternalUserNote[]           @relation("UserRecievedNotes")
  addedInternalNotes       InternalUserNote[]           @relation("UserAddedNotes")
  addedServiceNotes        InternalServiceNote[]        @relation("UserAddedServiceNotes")
  verificationRequests     ServiceVerificationRequest[]
  notifications            Notification[]               @relation("NotificationOwner")
  notificationPreferences  NotificationPreferences?
  serviceAffiliations      ServiceUser[]                @relation("UserServices")
  pushSubscriptions        PushSubscription[]

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

model CommentVote {
  id        Int      @id @default(autoincrement())
  downvote  Boolean  @default(false) // false = upvote, true = downvote
  comment   Comment  @relation(fields: [commentId], references: [id], onDelete: Cascade)
  commentId Int
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    Int
  createdAt DateTime @default(now())

  @@unique([commentId, userId]) // Ensure one vote per user per comment
  @@index([commentId])
  @@index([userId])
}

model ServiceAttribute {
  service     Service   @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId   Int
  attribute   Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade)
  attributeId Int
  createdAt   DateTime  @default(now())

  @@id([serviceId, attributeId])
}

model KarmaTransaction {
  id              Int                    @id @default(autoincrement())
  user            User                   @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId          Int
  action          KarmaTransactionAction
  points          Int                    @default(0)
  comment         Comment?               @relation(fields: [commentId], references: [id], onDelete: Cascade)
  commentId       Int?
  suggestion      ServiceSuggestion?     @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
  suggestionId    Int?
  description     String
  processed       Boolean                @default(false)
  createdAt       DateTime               @default(now())
  grantedBy       User?                  @relation("KarmaGrantedBy", fields: [grantedByUserId], references: [id], onDelete: SetNull)
  grantedByUserId Int?
  Notification    Notification[]

  @@index([createdAt])
  @@index([userId])
  @@index([processed])
  @@index([suggestionId])
  @@index([commentId])
  @@index([grantedByUserId])
}

enum VerificationStepStatus {
  PENDING
  IN_PROGRESS
  PASSED
  FAILED
  WARNING
}

model VerificationStep {
  id          Int                    @id @default(autoincrement())
  title       String
  description String
  status      VerificationStepStatus @default(PENDING)
  evidenceMd  String?
  createdAt   DateTime               @default(now())
  updatedAt   DateTime               @default(now()) @updatedAt
  service     Service                @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId   Int

  @@index([serviceId])
  @@index([status])
  @@index([createdAt])
}

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

  createdAt DateTime  @default(now())
  updatedAt DateTime  @default(now()) @updatedAt
  services  Service[] @relation("ServiceToCategory")

  notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]

  @@index([name])
  @@index([slug])
}

model ServiceVerificationRequest {
  id        Int      @id @default(autoincrement())
  service   Service  @relation(fields: [serviceId], references: [id], onDelete: Cascade)
  serviceId Int
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    Int
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

  @@unique([serviceId, userId])
  @@index([serviceId])
  @@index([userId])
  @@index([createdAt])
}

model ServiceScoreRecalculationJob {
  id          Int       @id @default(autoincrement())
  serviceId   Int       @unique
  createdAt   DateTime  @default(now())
  processedAt DateTime? @updatedAt

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

model ServiceUser {
  id        Int             @id @default(autoincrement())
  userId    Int
  user      User            @relation("UserServices", fields: [userId], references: [id], onDelete: Cascade)
  serviceId Int
  service   Service         @relation("ServiceUsers", fields: [serviceId], references: [id], onDelete: Cascade)
  role      ServiceUserRole
  createdAt DateTime        @default(now())
  updatedAt DateTime        @updatedAt

  @@unique([userId, serviceId])
  @@index([userId])
  @@index([serviceId])
  @@index([role])
}

model Announcement {
  id        Int              @id @default(autoincrement())
  content   String
  type      AnnouncementType
  link      String?
  linkText  String?
  startDate DateTime
  endDate   DateTime?
  isActive  Boolean          @default(true)
  createdAt DateTime         @default(now())
  updatedAt DateTime         @default(now()) @updatedAt

  @@index([isActive, startDate, endDate])
}

model PushSubscription {
  id        Int      @id @default(autoincrement())
  userId    Int
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  endpoint  String   @unique
  /// Public key for encryption
  p256dh    String
  /// Authentication secret
  auth      String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

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