generator client {
  provider = "prisma-client"
  output   = "../app/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model User {
  id                      String    @id @default(cuid())
  name                    String
  email                   String    @unique
  /// Hash bcrypt. Null untuk user yang sign-in via OAuth (mis. Google).
  password                String?
  image                   String?
  /// Diisi PrismaAdapter NextAuth saat email diverifikasi provider OAuth (Google selalu sudah verified).
  emailVerified           DateTime?
  /// Apakah user telah menyetujui Syarat & Ketentuan dan Kebijakan Privasi
  acceptedTermsAndPrivacy Boolean   @default(false)
  /// Waktu user menyetujui Syarat & Ketentuan dan Kebijakan Privasi
  acceptedAt              DateTime?
  createdAt               DateTime  @default(now())
  updatedAt               DateTime  @updatedAt

  accounts       Account[]
  trips          Trip[]
  participations TripParticipant[]
  tripReviews    TripReview[]
  bookings       Booking[]

  organizerVerification OrganizerVerification?  @relation("OrganizerVerificationOwner")
  reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")

  profile UserProfile?
}

/// Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
/// (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
model UserProfile {
  id     String @id @default(cuid())
  userId String @unique
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)

  /// Bio singkat, teks bebas
  bio       String?
  /// Kota domisili (teks bebas, mis. "Bandung", "Jakarta Selatan")
  city      String?
  /// Tag minat aktivitas (mis. ["hiking", "fotografi", "yoga"])
  interests String[] @default([])
  /// Username Instagram (tanpa @, opsional)
  instagram String?
  /// Gaya jalan / energi user — dipakai untuk matching teman dengan ritme serupa.
  vibe      Vibe?

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

enum Vibe {
  CHILL
  BALANCED
  HARDCORE
}

/// Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
/// Session tidak pakai DB — kita pakai JWT, jadi Session/VerificationToken tidak perlu.
model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?

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

  @@unique([provider, providerAccountId])
}

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

  /// Nama lengkap sesuai KTP
  fullName     String
  /// NIK terenkripsi (AES-256-GCM, base64). Plaintext tidak disimpan.
  nikEncrypted String
  /// HMAC-SHA256(NIK + pepper) untuk uniqueness lookup tanpa membuka plaintext.
  nikHash      String   @unique
  birthDate    DateTime
  address      String

  /// Storage key foto KTP (mis. `ktp/<id>.jpg`). File disimpan terenkripsi di luar /public.
  ktpImageKey String
  /// Storage key foto liveness — user memegang kertas bertuliskan "SETRIP".
  /// (Sebelumnya: selfie memegang KTP. Diganti supaya user tidak perlu memajang KTP dua kali.)
  livenessKey String

  bankName          String
  bankAccountNumber String
  bankAccountName   String

  status          VerificationStatus @default(PENDING)
  rejectionReason String?
  reviewedAt      DateTime?
  reviewedById    String?
  reviewedBy      User?              @relation("OrganizerVerificationReviewer", fields: [reviewedById], references: [id])
  verifiedAt      DateTime?

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

enum VerificationStatus {
  PENDING
  APPROVED
  REJECTED
}

model Trip {
  id              String           @id @default(cuid())
  title           String
  description     String?
  /// Kategori aktivitas.
  category        ActivityCategory @default(HIKING)
  /// Destinasi/subjek aktivitas — interpretasinya tergantung kategori (gunung untuk HIKING, spot untuk DIVING, venue untuk CONCERT, tema untuk WORKSHOP, dst).
  destination     String
  location        String
  /// Titik kumpul / meeting point (teks bebas)
  meetingPoint    String?
  /// Itinerary hari per hari (teks bebas, bullet OK)
  itinerary       String?
  /// Yang termasuk harga (teks bebas)
  whatsIncluded   String?
  /// Yang tidak termasuk (teks bebas)
  whatsExcluded   String?
  date            DateTime
  endDate         DateTime?
  maxParticipants Int
  price           Int
  /// Ritme/energi trip — dipakai untuk matching dengan vibe user.
  vibe            Vibe?
  status          TripStatus       @default(OPEN)
  createdAt       DateTime         @default(now())
  updatedAt       DateTime         @updatedAt

  organizerId String
  organizer   User   @relation(fields: [organizerId], references: [id])

  participants TripParticipant[]
  images       TripImage[]
  reviews      TripReview[]
  bookings     Booking[]

  @@index([category, status, date])
  @@index([vibe, status, date])
}

model TripReview {
  id        String   @id @default(cuid())
  rating    Int
  comment   String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  tripId String
  trip   Trip   @relation(fields: [tripId], references: [id], onDelete: Cascade)

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

  @@unique([tripId, userId])
}

model TripImage {
  id      String  @id @default(cuid())
  url     String
  caption String?
  order   Int     @default(0)

  tripId String
  trip   Trip   @relation(fields: [tripId], references: [id], onDelete: Cascade)
}

model TripParticipant {
  id                 String            @id @default(cuid())
  status             ParticipantStatus @default(PENDING)
  createdAt          DateTime          @default(now())
  /// @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
  /// untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
  markedPaidAt       DateTime?
  /// @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
  /// untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
  paymentConfirmedAt DateTime?

  tripId String
  trip   Trip   @relation(fields: [tripId], references: [id])

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

  booking Booking?

  @@unique([tripId, userId])
}

enum TripStatus {
  OPEN
  FULL
  CLOSED
  COMPLETED
}

enum ActivityCategory {
  HIKING
  CAMPING
  SNORKELING
  DIVING
  ISLAND_HOPPING
  CITY_TRIP
  CULINARY
  CONCERT
  WORKSHOP
  RETREAT
}

enum ParticipantStatus {
  PENDING
  CONFIRMED
  CANCELLED
}

/// Booking 1-1 ke TripParticipant. Lifecycle ikut peserta:
/// - join → Booking PENDING (menunggu approve organizer)
/// - organizer confirm → AWAITING_PAY (paid trip) atau PAID (free trip)
/// - peserta + organizer rampungkan pembayaran → PAID
/// - cancel/reject → CANCELLED
/// `amount` adalah snapshot harga saat booking dibuat — protect dari perubahan trip.price.
model Booking {
  id            String          @id @default(cuid())
  tripId        String
  trip          Trip            @relation(fields: [tripId], references: [id])
  userId        String
  user          User            @relation(fields: [userId], references: [id])
  participantId String          @unique
  participant   TripParticipant @relation(fields: [participantId], references: [id], onDelete: Cascade)

  amount   Int
  currency String        @default("IDR")
  status   BookingStatus @default(PENDING)

  payments Payment[]

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

  /// Konsistensi: 1-1 ke participant via participantId, dan participant unique
  /// per (tripId, userId). Constraint ini eksplisit + jadi index untuk query
  /// `findByTripAndUser`.
  @@unique([tripId, userId])
  @@index([tripId, status])
}

enum BookingStatus {
  PENDING
  AWAITING_PAY
  PAID
  CANCELLED
  REFUNDED
  EXPIRED
}

/// Satu attempt pembayaran. Satu Booking bisa punya banyak Payment kalau retry
/// (di Phase MIDTRANS nanti). Untuk MANUAL biasanya cukup 1 Payment.
model Payment {
  id        String  @id @default(cuid())
  bookingId String
  booking   Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)

  provider        PaymentProvider
  /// order_id eksternal (unik per attempt). Format MANUAL: `manual-<bookingId>`.
  /// Format MIDTRANS nanti: `midtrans-<bookingId>-<retry>`.
  externalOrderId String          @unique
  /// transaction_id dari gateway. Kosong untuk MANUAL atau sebelum first callback.
  externalTxId    String?
  /// Metode konkret: bca_va, gopay, qris, manual_transfer, dst.
  method          String?
  amount          Int
  status          PaymentStatus   @default(PENDING)

  /// Snapshot mentah callback gateway (untuk audit & dispute).
  rawCallback Json?
  /// Snap token Midtrans / redirect URL.
  snapToken   String?
  /// Kapan attempt ini kadaluarsa (Midtrans default 24 jam).
  expiresAt   DateTime?

  paidAt          DateTime?
  failedAt        DateTime?
  rejectionReason String?

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

  @@index([bookingId, status])
  @@index([provider, status])
}

enum PaymentProvider {
  MANUAL
  MIDTRANS
}

enum PaymentStatus {
  PENDING
  AWAITING
  PAID
  FAILED
  EXPIRED
  CANCELLED
  REFUNDED
}
