// =============================================================
// CYBER STAR · Prisma Schema
// 数据库：MySQL 8 · 部署：火山引擎 RDS
// =============================================================

generator client {
  provider      = "prisma-client-js"
  // native：本地开发（macOS / linux-glibc）
  // linux-musl-openssl-3.0.x：容器运行时（node:22-alpine）
  binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}

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

// =============================================================
// 艺人 · 候选偶像
// =============================================================
model Artist {
  id          String   @id @db.VarChar(8) // 编号 001 ~ 036
  no          String   @unique @db.VarChar(8) // 展示用编号（带前置零）
  name        String   @db.VarChar(50) // 中文名
  enName      String   @map("en_name") @db.VarChar(50) // 英文名
  bio         String   @db.Text // 详细简介 / 人物小传
  height      Int      @db.SmallInt // cm

  // 人物小传字段（从《36 位虚拟艺人人物小传.docx》提取）
  age         Int?     @db.SmallInt // 年龄
  gender      String?  @db.VarChar(1) // 'M' / 'F' / 'N'
  motto       String?  @db.VarChar(200) // 座右铭
  personality String?  @db.Text // 性格描述
  catchphrase String?  @db.VarChar(200) // 口头禅
  skills      String?  @db.VarChar(200) // 核心技能（顿号分隔）
  track       String?  @db.VarChar(200) // 核心赛道（顿号分隔）

  // 媒体资源（URL 走 TOS 桶 + CDN）
  portrait    String?  @db.VarChar(500)
  avatar      String?  @db.VarChar(500)
  videoUrl    String?  @map("video_url") @db.VarChar(500)
  videoPoster String?  @map("video_poster") @db.VarChar(500)

  // 历史遗留字段（前端已不再使用，保留为可选避免破坏既有数据）
  slogan      String?  @db.VarChar(120)
  birthday    String?  @db.VarChar(8) // MM-DD
  cv          String?  @db.VarChar(80)
  themeColor  String?  @map("theme_color") @db.VarChar(10)

  tags        Json     @db.Json // ArtistTag[] · 现在是音乐流派 (rock/pop/chinese/hiphop/folk/jazz)
  status      ArtistStatus @default(ACTIVE)
  /// 缓存字段：当前票数。定期由后台聚合任务更新，避免实时 SUM(votes)。
  voteCount   Int      @default(0) @map("vote_count")
  /// 缓存字段：当前排名。同上由后台计算。
  currentRank Int?     @map("current_rank")
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

  images  ArtistImage[]
  votes   Vote[]
  supports FanSupport[]
  snapshots RankingSnapshot[]

  @@map("artists")
  @@index([voteCount(sort: Desc)])
}

enum ArtistStatus {
  ACTIVE      // 正常参赛
  WITHDRAWN   // 退赛
  DISQUALIFIED // 取消资格
}

// 艺人多张展示图（定妆/表演/幕后等）
model ArtistImage {
  id        BigInt   @id @default(autoincrement())
  artistId  String   @map("artist_id") @db.VarChar(8)
  url       String   @db.VarChar(500)
  type      String   @db.VarChar(20) // 'portrait' | 'performance' | 'backstage' | 'stage'
  sortOrder Int      @default(0) @map("sort_order")
  createdAt DateTime @default(now()) @map("created_at")

  artist Artist @relation(fields: [artistId], references: [id], onDelete: Cascade)

  @@map("artist_images")
  @@index([artistId, sortOrder])
}

// =============================================================
// 用户
// =============================================================
model User {
  id                BigInt   @id @default(autoincrement())
  phone             String?  @unique @db.VarChar(20) // E.164 格式
  email             String?  @unique @db.VarChar(120)
  openId            String?  @unique @map("open_id") @db.VarChar(120) // 微信 openid
  unionId           String?  @unique @map("union_id") @db.VarChar(120) // 微信 unionid
  nickname          String   @db.VarChar(80)
  avatar            String?  @db.VarChar(500)
  loginType         LoginType @default(PHONE) @map("login_type")
  registerIp        String?  @map("register_ip") @db.VarChar(45) // IPv4/IPv6
  deviceFingerprint String?  @map("device_fingerprint") @db.VarChar(120)
  /// 风控等级 · 数值越大风险越高，由风控系统更新
  riskLevel         Int      @default(0) @map("risk_level") @db.TinyInt
  status            UserStatus @default(NORMAL)
  lastLoginAt       DateTime? @map("last_login_at")
  createdAt         DateTime @default(now()) @map("created_at")
  updatedAt         DateTime @updatedAt @map("updated_at")

  votes      Vote[]
  quotas     DailyQuota[]
  signIns    SignIn[]
  supports   FanSupport[]
  invitedBy  Invitation[] @relation("invitee")
  invitations Invitation[] @relation("inviter")

  @@map("users")
  @@index([phone])
  @@index([deviceFingerprint])
}

enum LoginType {
  PHONE   // 手机号 + OTP
  WECHAT  // 微信扫码
  EMAIL   // 邮箱（境外）
}

enum UserStatus {
  NORMAL   // 正常
  WARNED   // 警告
  BANNED   // 封禁
}

// =============================================================
// 投票记录（核心热表）
// =============================================================
model Vote {
  id        BigInt   @id @default(autoincrement())
  userId    BigInt   @map("user_id")
  artistId  String   @map("artist_id") @db.VarChar(8)
  count     Int      @default(1) // 单次投票数
  source    VoteSource @default(QUOTA)
  ip        String?  @db.VarChar(45)
  ua        String?  @db.VarChar(500) // user agent
  fingerprint String? @db.VarChar(120) // 设备指纹快照
  createdAt DateTime @default(now()) @map("created_at")

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

  @@map("votes")
  // 投票规则:每用户对每艺人仅可投 1 票 —— DB 硬约束,防止并发绕过前端
  @@unique([userId, artistId])
  // 关键索引:艺人聚合 / 时序查询
  @@index([artistId, createdAt])
  @@index([createdAt])
}

enum VoteSource {
  QUOTA      // 每日基础票
  SIGNIN     // 签到额外票
  SHARE      // 分享得票
  INVITE     // 邀请奖励
  PAID       // 付费购票（若开放）
}

// =============================================================
// 每日票数余额
// =============================================================
model DailyQuota {
  id         BigInt   @id @default(autoincrement())
  userId     BigInt   @map("user_id")
  date       DateTime @db.Date
  totalQuota Int      @default(12) @map("total_quota") // 当日总票数（基础 + 奖励）
  usedQuota  Int      @default(0) @map("used_quota") // 已用
  createdAt  DateTime @default(now()) @map("created_at")
  updatedAt  DateTime @updatedAt @map("updated_at")

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

  @@map("daily_quota")
  @@unique([userId, date])
}

// =============================================================
// 签到记录
// =============================================================
model SignIn {
  id          BigInt   @id @default(autoincrement())
  userId      BigInt   @map("user_id")
  date        DateTime @db.Date
  streak      Int      @default(1) // 连续签到天数
  bonusVotes  Int      @default(1) @map("bonus_votes") // 奖励票数
  createdAt   DateTime @default(now()) @map("created_at")

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

  @@map("sign_ins")
  @@unique([userId, date])
}

// =============================================================
// 应援关系（用户 ❤ 艺人）· 用于"我的应援"区
// =============================================================
model FanSupport {
  id        BigInt   @id @default(autoincrement())
  userId    BigInt   @map("user_id")
  artistId  String   @map("artist_id") @db.VarChar(8)
  votedTotal Int      @default(0) @map("voted_total") // 累计已投给该艺人的票数
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@map("fan_supports")
  @@unique([userId, artistId])
}

// =============================================================
// 邀请记录（病毒传播）
// =============================================================
model Invitation {
  id          BigInt   @id @default(autoincrement())
  inviterId   BigInt   @map("inviter_id")
  inviteeId   BigInt   @unique @map("invitee_id") // 被邀请人只能被邀请一次
  bonusGiven  Boolean  @default(false) @map("bonus_given") // 是否已发放奖励
  createdAt   DateTime @default(now()) @map("created_at")

  inviter User @relation("inviter", fields: [inviterId], references: [id], onDelete: Cascade)
  invitee User @relation("invitee", fields: [inviteeId], references: [id], onDelete: Cascade)

  @@map("invitations")
  @@index([inviterId])
}

// =============================================================
// 排名快照（用于历史趋势 / 大屏展示）
// =============================================================
model RankingSnapshot {
  id        BigInt   @id @default(autoincrement())
  artistId  String   @map("artist_id") @db.VarChar(8)
  date      DateTime @db.DateTime(0) // 精确到小时的快照
  voteCount Int      @map("vote_count")
  rank      Int

  artist Artist @relation(fields: [artistId], references: [id], onDelete: Cascade)

  @@map("ranking_snapshots")
  @@unique([artistId, date])
  @@index([date])
}

// =============================================================
// 活动配置（开关 / 时间 / 规则）
// =============================================================
model ActivityConfig {
  id            Int      @id @default(1) // 单行配置
  startAt       DateTime @map("start_at")
  endAt         DateTime @map("end_at")
  voteEnabled   Boolean  @default(true) @map("vote_enabled") // 紧急停止开关
  dailyQuota    Int      @default(12) @map("daily_quota")
  perArtistLimit Int      @default(3) @map("per_artist_limit") // 每艺人每日上限
  paidVoteEnabled Boolean @default(false) @map("paid_vote_enabled")
  updatedAt     DateTime @updatedAt @map("updated_at")

  @@map("activity_config")
}

// =============================================================
// 风控日志（异常投票审计）
// =============================================================
model RiskLog {
  id        BigInt   @id @default(autoincrement())
  userId    BigInt?  @map("user_id")
  ip        String?  @db.VarChar(45)
  fingerprint String? @db.VarChar(120)
  rule      String   @db.VarChar(80) // 命中的风控规则
  detail    Json?    @db.Json
  action    RiskAction @default(WARN)
  createdAt DateTime @default(now()) @map("created_at")

  @@map("risk_logs")
  @@index([userId, createdAt])
  @@index([ip, createdAt])
}

enum RiskAction {
  WARN     // 仅记录
  CAPTCHA  // 触发验证码
  BLOCK    // 拦截本次操作
  BAN      // 封号
}
