/**
 * ReadMeABook Database Schema
 * Documentation: documentation/backend/database.md
 *
 * ARCHITECTURE:
 * - audible_cache: Pure Audible metadata (popular/new releases from Audible.com)
 * - plex_library: Pure Plex library content (what's in your Plex server)
 * - audiobooks: User-requested audiobooks only (created on request)
 * - Matching happens at QUERY TIME by comparing audible_cache against plex_library
 */

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

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

// ============================================================================
// MODELS
// ============================================================================

model User {
  id            String    @id @default(uuid())
  plexId        String    @unique @map("plex_id")
  plexUsername  String    @map("plex_username")
  plexEmail     String?   @map("plex_email")
  role          String    @default("user") // 'user' or 'admin'
  isSetupAdmin  Boolean   @default(false) @map("is_setup_admin") // First admin created during setup, cannot be demoted
  avatarUrl     String?   @map("avatar_url")
  authToken     String?   @map("auth_token") // Encrypted
  createdAt     DateTime  @default(now()) @map("created_at")
  updatedAt     DateTime  @updatedAt @map("updated_at")
  lastLoginAt   DateTime? @map("last_login_at")

  // Plex Home profile tracking
  plexHomeUserId String?  @map("plex_home_user_id") // Profile ID from Plex Home (null = main account, set = home profile)

  // Multi-auth support (for Audiobookshelf integration)
  authProvider        String?  @map("auth_provider")  // 'plex' | 'oidc' | 'local'
  oidcSubject         String?  @map("oidc_subject")   // OIDC subject ID (unique per provider)
  oidcProvider        String?  @map("oidc_provider")  // OIDC provider name (e.g., 'authentik', 'keycloak')
  registrationStatus  String?  @default("approved") @map("registration_status")  // 'pending_approval' | 'approved' | 'rejected'

  // BookDate per-user preferences
  bookDateLibraryScope         String?  @default("full") @map("bookdate_library_scope") // 'full' | 'rated'
  bookDateCustomPrompt         String?  @map("bookdate_custom_prompt") @db.Text
  bookDateOnboardingComplete   Boolean  @default(false) @map("bookdate_onboarding_complete")

  // Relations
  requests                Request[]
  bookDateRecommendations BookDateRecommendation[]
  bookDateSwipes          BookDateSwipe[]

  @@index([plexId])
  @@index([role])
  @@map("users")
}

// ============================================================================
// AUDIBLE CACHE TABLE
// Pure Audible metadata - Popular/New Releases cached from Audible.com
// No Plex data, no availability status - just Audible metadata
// ============================================================================
model AudibleCache {
  id              String    @id @default(uuid())
  asin            String    @unique // Audible Standard Identification Number
  title           String
  author          String
  narrator        String?
  description     String?   @db.Text
  coverArtUrl       String?   @map("cover_art_url") @db.Text
  cachedCoverPath   String?   @map("cached_cover_path") @db.Text // Local path to cached cover image
  durationMinutes   Int?      @map("duration_minutes")
  releaseDate     DateTime? @map("release_date") @db.Date
  rating          Decimal?  @db.Decimal(3, 2)
  genres          Json      @default("[]")

  // Discovery categories
  isPopular      Boolean   @default(false) @map("is_popular")
  isNewRelease   Boolean   @default(false) @map("is_new_release")
  popularRank    Int?      @map("popular_rank")
  newReleaseRank Int?      @map("new_release_rank")

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

  @@index([asin])
  @@index([title])
  @@index([author])
  @@index([isPopular])
  @@index([isNewRelease])
  @@index([popularRank])
  @@index([newReleaseRank])
  @@map("audible_cache")
}

// ============================================================================
// LIBRARY CACHE TABLE (plex_library for backward compatibility)
// Universal library content - Works with Plex or Audiobookshelf backends
// Stores complete metadata including ASIN/ISBN for accurate matching
// No Audible data - just library backend metadata and file info
// ============================================================================
model PlexLibrary {
  id            String    @id @default(uuid())
  plexGuid      String    @unique @map("plex_guid") // Plex's unique identifier
  plexRatingKey String?   @map("plex_rating_key") // Plex's rating key

  title         String
  author        String
  narrator      String?
  summary       String?   @db.Text
  duration      Int?      // Duration in milliseconds (Plex format)
  year          Int?
  userRating    Decimal?  @map("user_rating") @db.Decimal(3, 1) // User's rating (0-10 scale from Plex)

  // Universal identifiers (works for both Plex and Audiobookshelf)
  asin          String?   // Audible ASIN - extracted from Plex GUID or stored directly from ABS
  isbn          String?   // ISBN (10 or 13) - for additional matching capability

  // File information
  filePath      String?   @map("file_path") @db.Text
  thumbUrl      String?   @map("thumb_url") @db.Text // Plex thumbnail URL

  // Plex metadata
  plexLibraryId String    @map("plex_library_id") // Which Plex library contains this
  addedAt       DateTime? @map("added_at") // When added to Plex

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

  @@index([plexGuid])
  @@index([title])
  @@index([author])
  @@index([plexLibraryId])
  @@index([asin])
  @@index([isbn])
  @@map("plex_library")
}

// ============================================================================
// AUDIOBOOK TABLE (Simplified)
// Only created when user requests an audiobook
// Links to AudibleCache for metadata (optional - search results may not be cached)
// ============================================================================
model Audiobook {
  id              String    @id @default(uuid())

  // Core metadata (may come from Audible search, not necessarily cached)
  audibleAsin     String?   @map("audible_asin") // ASIN if from Audible
  title           String
  author          String
  narrator        String?
  description     String?   @db.Text
  coverArtUrl     String?   @map("cover_art_url") @db.Text

  // Request tracking
  status          String    @default("requested") // requested, downloading, processing, completed, failed

  // File information (populated after download/organization)
  filePath        String?   @map("file_path") @db.Text
  fileFormat      String?   @map("file_format") // m4b, m4a, mp3
  fileSizeBytes   BigInt?   @map("file_size_bytes")

  // Plex integration (populated after successful import)
  plexGuid        String?   @map("plex_guid") // Set when imported into Plex
  plexLibraryId   String?   @map("plex_library_id")

  // Audiobookshelf integration (alternative to Plex)
  absItemId       String?   @map("abs_item_id") // Audiobookshelf item ID

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

  // Relations
  requests Request[]

  @@index([audibleAsin])
  @@index([plexGuid])
  @@index([absItemId])
  @@index([title])
  @@index([author])
  @@index([status])
  @@map("audiobooks")
}

model Request {
  id               String    @id @default(uuid())
  userId           String    @map("user_id")
  audiobookId      String    @map("audiobook_id")
  status           String    @default("pending")
  // Status values: pending, searching, downloading, processing, downloaded, available, failed, cancelled, awaiting_search, awaiting_import, warn
  // Flow: pending → searching → downloading → processing → downloaded → available (when matched in Plex)
  progress         Int       @default(0) // 0-100
  priority         Int       @default(0)
  errorMessage     String?   @map("error_message") @db.Text
  searchAttempts   Int       @default(0) @map("search_attempts")
  downloadAttempts Int       @default(0) @map("download_attempts")
  importAttempts   Int       @default(0) @map("import_attempts")
  maxImportRetries Int       @default(5) @map("max_import_retries")
  lastSearchAt     DateTime? @map("last_search_at")
  lastImportAt     DateTime? @map("last_import_at")
  createdAt        DateTime  @default(now()) @map("created_at")
  updatedAt        DateTime  @updatedAt @map("updated_at")
  completedAt      DateTime? @map("completed_at")

  // Relations
  user             User               @relation(fields: [userId], references: [id], onDelete: Cascade)
  audiobook        Audiobook          @relation(fields: [audiobookId], references: [id], onDelete: Cascade)
  downloadHistory  DownloadHistory[]
  jobs             Job[]

  @@unique([userId, audiobookId])
  @@index([userId])
  @@index([audiobookId])
  @@index([status])
  @@index([createdAt(sort: Desc)])
  @@map("requests")
}

model DownloadHistory {
  id               String    @id @default(uuid())
  requestId        String    @map("request_id")
  indexerName      String    @map("indexer_name")
  torrentName      String?   @map("torrent_name")
  torrentHash      String?   @map("torrent_hash")
  torrentSizeBytes BigInt?   @map("torrent_size_bytes")
  magnetLink       String?   @map("magnet_link") @db.Text
  torrentUrl       String?   @map("torrent_url") @db.Text
  seeders          Int?
  leechers         Int?
  qualityScore     Int?      @map("quality_score")
  selected         Boolean   @default(false)
  downloadClient   String?   @map("download_client") // qbittorrent, transmission
  downloadClientId String?   @map("download_client_id")
  downloadStatus   String?   @map("download_status")
  // Status values: queued, downloading, completed, failed, stalled
  downloadError    String?   @map("download_error") @db.Text
  startedAt        DateTime? @map("started_at")
  completedAt      DateTime? @map("completed_at")
  createdAt        DateTime  @default(now()) @map("created_at")

  // Relations
  request Request @relation(fields: [requestId], references: [id], onDelete: Cascade)

  @@index([requestId])
  @@index([selected])
  @@index([createdAt(sort: Desc)])
  @@map("download_history")
}

model Configuration {
  id          String   @id @default(uuid())
  key         String   @unique
  value       String?  @db.Text
  encrypted   Boolean  @default(false)
  category    String? // plex, indexer, download_client, system, automation
  description String?  @db.Text
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

  @@index([key])
  @@index([category])
  @@map("configuration")
}

model Job {
  id          String    @id @default(uuid())
  bullJobId   String?   @map("bull_job_id")
  requestId   String?   @map("request_id")
  type        String
  // Job types: search_indexers, monitor_download, organize_files, scan_plex, plex_recently_added_check, match_plex
  status      String    @default("pending")
  // Status values: pending, active, completed, failed, delayed, stuck
  priority    Int       @default(0)
  attempts    Int       @default(0)
  maxAttempts Int       @default(3) @map("max_attempts")
  payload     Json?
  result      Json?
  errorMessage String?   @map("error_message") @db.Text
  stackTrace  String?   @map("stack_trace") @db.Text
  startedAt   DateTime? @map("started_at")
  completedAt DateTime? @map("completed_at")
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  // Relations
  request Request? @relation(fields: [requestId], references: [id], onDelete: SetNull)
  events  JobEvent[]

  @@index([requestId])
  @@index([type])
  @@index([status])
  @@index([createdAt(sort: Desc)])
  @@map("jobs")
}

model JobEvent {
  id        String   @id @default(uuid())
  jobId     String   @map("job_id")
  level     String   // info, warn, error
  context   String   // e.g., OrganizeFiles, FileOrganizer, MonitorDownload
  message   String   @db.Text
  metadata  Json?    // Additional structured data
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)

  @@index([jobId])
  @@index([createdAt])
  @@map("job_events")
}

model ScheduledJob {
  id          String    @id @default(uuid())
  name        String
  type        String    // 'plex_library_scan', 'plex_recently_added_check', 'audible_refresh', 'retry_missing_torrents', 'retry_failed_imports', 'cleanup_seeded_torrents', 'monitor_rss_feeds'
  schedule    String    // Cron expression
  enabled     Boolean   @default(true)
  payload     Json      @default("{}")
  lastRun     DateTime? @map("last_run")
  lastRunJobId String?  @map("last_run_job_id")  // Bull queue job ID of most recent execution
  nextRun     DateTime? @map("next_run")
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  @@index([type])
  @@index([enabled])
  @@map("scheduled_jobs")
}

// ============================================================================
// BOOKDATE TABLES
// AI-powered audiobook recommendation system
// Documentation: documentation/features/bookdate-prd.md
// ============================================================================

model BookDateConfig {
  id            String   @id @default(uuid())
  provider      String   // 'openai' | 'claude'
  apiKey        String   @map("api_key") @db.Text // Encrypted at rest (AES-256)
  model         String   // e.g., 'gpt-4o', 'claude-sonnet-4-5-20250929'
  libraryScope  String?  @map("library_scope") // DEPRECATED: Now per-user (User.bookDateLibraryScope)
  customPrompt  String?  @map("custom_prompt") @db.Text // DEPRECATED: Now per-user (User.bookDateCustomPrompt)
  isVerified    Boolean  @default(false) @map("is_verified")
  isEnabled     Boolean  @default(true) @map("is_enabled") // Admin toggle (global feature)
  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")

  @@map("bookdate_config")
}

// Note: BookDateConfig is now a singleton - only ONE record exists globally.
// Admin configures this in settings, and all users share the same API key.
// Individual users still have their own recommendations and swipe history.

model BookDateRecommendation {
  id             String    @id @default(uuid())
  userId         String    @map("user_id")
  batchId        String    @map("batch_id") // Group recommendations from same AI call
  title          String
  author         String
  narrator       String?
  rating         Decimal?  @db.Decimal(3, 2)
  description    String?   @db.Text
  coverUrl       String?   @map("cover_url") @db.Text
  audnexusAsin   String?   @map("audnexus_asin") // For matching
  aiReason       String    @map("ai_reason") @db.Text // Why AI recommended this
  createdAt      DateTime  @default(now()) @map("created_at")

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

  @@index([userId, batchId])
  @@index([userId, createdAt])
  @@map("bookdate_recommendations")
}

model BookDateSwipe {
  id               String    @id @default(uuid())
  userId           String    @map("user_id")
  recommendationId String?   @map("recommendation_id") // NULL if book not from BookDate
  bookTitle        String    @map("book_title")
  bookAuthor       String    @map("book_author")
  action           String    // 'left' | 'right' | 'up'
  markedAsKnown    Boolean   @default(false) @map("marked_as_known") // True if "Mark as Known"
  createdAt        DateTime  @default(now()) @map("created_at")

  // Relations
  user           User                    @relation(fields: [userId], references: [id], onDelete: Cascade)
  recommendation BookDateRecommendation? @relation(fields: [recommendationId], references: [id], onDelete: SetNull)

  @@index([userId, createdAt])
  @@index([recommendationId])
  @@map("bookdate_swipes")
}
