// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "linux-musl-openssl-3.0.x", "debian-openssl-3.0.x", "linux-musl-arm64-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
}

datasource db {
  provider = "postgresql"
}

// ==================== 枚举类型 ====================

enum UserRole {
  admin
  user
}

enum UserStatus {
  active
  banned
}

enum HostStatus {
  online
  offline
  maintenance
}

enum HostAddressKind {
  domain
  ipv4
  ipv6
}

enum HostAddressSource {
  input
  resolved
}

enum HostAddressCheckTrigger {
  create
  update
  backfill
  poll
}

enum HostAddressCheckStatus {
  success
  failed
}

enum HostAddressConflictStatus {
  active
  resolved
}

enum InstanceType {
  container
  vm
  both
}

enum NetworkMode {
  nat          // 纯 IPv4 NAT
  nat_ipv6     // IPv4 NAT + IPv6 独立（Routed）
  nat_ipv6_nat // IPv4 NAT + IPv6 NAT（共享宿主机 IPv6）
  ipv6_only    // 纯 IPv6 独立（Routed），无 NAT 端口映射
  ipv6_nat     // 纯 IPv6 NAT（共享宿主机 IPv6），无 NAT 端口映射
}

// 存储池用途枚举
enum StoragePurpose {
  instance_data // 用于实例数据盘（创建实例时默认存储）
  instance_storage // 用于实例存储盘（可手动挂载）
}

// 反代站点状态枚举
enum ProxySiteStatus {
  pending // 等待 DNS 解析
  active // 已生效
  error // 配置失败
}

enum InstanceStatus {
  creating
  running
  stopped
  suspended // 封停状态（到期或手动封停）
  error
  deleted
}

enum Protocol {
  tcp
  udp
}

enum BackupStatus {
  creating
  ready
  error
  deleted
}

enum RestoreTaskStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

// 存储类型枚举
enum StorageType {
  S3 // 预留
  WEBDAV
  FTP
  SFTP
}

// 备份上传任务状态
enum BackupUploadTaskStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

// 实例操作任务类型
enum InstanceTaskType {
  start
  stop
  restart
  rebuild
  clone
  recreate  // 重建：创建新实例替换旧实例
  change_host // 改节点：在其他节点重建实例并保留数据库实例 ID
}

// 实例操作任务状态
enum InstanceTaskStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

enum HostNotificationEmailTaskStatus {
  PENDING
  PROCESSING
  SENT
  FAILED
}

// ==================== 恢复任务模型 ====================

model RestoreTask {
  id                   Int               @id @default(autoincrement())
  instanceId           Int               @map("instance_id")
  backupId             Int?              @map("backup_id")
  hostId               Int               @map("host_id")
  userId               Int               @map("user_id")
  status               RestoreTaskStatus @default(PENDING)
  progress             String? // 当前执行步骤: stopping, restoring, cleaning, replacing, starting
  retryCount           Int               @default(0) @map("retry_count") // 重试次数
  tempInstanceName     String?           @map("temp_instance_name")
  originalInstanceName String            @map("original_instance_name")
  originalIncusId      String            @map("original_incus_id")
  error                String?
  createdAt            DateTime          @default(now()) @map("created_at")
  startedAt            DateTime?         @map("started_at")
  finishedAt           DateTime?         @map("finished_at")

  @@index([hostId, status])
  @@index([instanceId])
  @@map("restore_tasks")
}

// ==================== 实例操作任务模型 ====================

model InstanceTask {
  id         Int                @id @default(autoincrement())
  instanceId Int                @map("instance_id")
  hostId     Int                @map("host_id")
  userId     Int                @map("user_id")
  taskType   InstanceTaskType   @map("task_type")
  status     InstanceTaskStatus @default(PENDING)
  progress   String? // 当前执行步骤
  retryCount Int                @default(0) @map("retry_count")

  // 重装/克隆操作特有字段
  imageAlias           String? @map("image_alias") // 重装使用的镜像别名
  sshKeyId             Int?    @map("ssh_key_id") // 重装使用的 SSH 密钥 ID
  customInitCommandIds String? @map("custom_init_command_ids") // 重装使用的自定义初始化命令ID列表 (JSON数组)
  targetName           String? @map("target_name") // 克隆的目标名称
  targetHostId         Int?    @map("target_host_id") // 克隆的目标宿主机
  snapshotName         String? @map("snapshot_name") // 克隆时的快照名称
  newInstanceId        Int?    @map("new_instance_id") // 克隆后的新实例 ID

  error      String?
  createdAt  DateTime  @default(now()) @map("created_at")
  startedAt  DateTime? @map("started_at")
  finishedAt DateTime? @map("finished_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([hostId, status])
  @@index([instanceId])
  @@index([userId])
  @@map("instance_tasks")
}

enum NotificationChannelType {
  telegram
  discord
  email
  webhook
}

enum NotificationLogStatus {
  pending
  sent
  failed
}

enum OAuthProvider {
  github
  google
}

// ==================== 计费相关枚举 ====================

// 余额变动类型
enum BalanceLogType {
  recharge          // 充值
  consume           // 消费（开通/续费实例）
  refund            // 退款
  admin_adjust      // 管理员调整
  gift              // 赠送
  transfer_fee      // 转移手续费
  transfer_refund   // 转移手续费退还
  hosting_withdraw  // 托管余额提现
  hosting_deduction // 托管实例销毁扣款（从面板余额补扣）
  invite_generate   // 生成邀请码扣款
}

enum VipBenefitRewardType {
  balance
  points
  instance
}

enum VipBenefitClaimStatus {
  delivered
  pending
}

// 支付渠道类型
enum PaymentProviderType {
  yipay         // 易支付
  heleket       // Heleket 加密支付
  stripe        // Stripe
  alipay_direct // 支付宝直连（预留）
  wechat_direct // 微信直连（预留）
  manual        // 人工充值
}

// 支付渠道状态
enum PaymentProviderStatus {
  active        // 启用
  disabled      // 禁用
  testing       // 测试模式
}

// 充值记录状态
enum RechargeStatus {
  pending       // 待支付
  paid          // 已支付，处理中
  completed     // 已完成（余额已到账）
  failed        // 失败
  cancelled     // 已取消
  refunded      // 已退款
}

enum VipLevelRuleType {
  user
  hosting
}

enum VipLevelConditionMode {
  any
  all
}

// 实例计费记录类型
enum BillingRecordType {
  newPurchase   // 新开
  renew         // 续费
  upgrade       // 升级
  downgrade     // 降级
  refund        // 退款（仅管理员操作）
  transfer_fee  // 转移手续费
}

// AFF余额变动类型
enum AffLogType {
  new_purchase  // 新购返利
  renew         // 续费返利
  convert       // 转化为主余额
}

// AFF转化申请状态
enum AffWithdrawalStatus {
  pending   // 待审核
  approved  // 已通过
  rejected  // 已拒绝
}

// 流量状态枚举
enum TrafficStatus {
  NORMAL // 正常
  WARNING // 已发送 80% 预警
  LIMITED // 已限速
}

// 好友关系状态枚举
enum FriendshipStatus {
  pending // 待确认
  accepted // 已接受
  rejected // 已拒绝
  removed // 已删除（双方都已不是好友，但保留历史记录）
}

// ==================== 用户相关模型 ====================

model User {
  id           Int        @id @default(autoincrement())
  username     String     @unique
  email        String?
  passwordHash String     @map("password_hash")
  role         UserRole   @default(user)
  status       UserStatus @default(active)
  banReason    String?    @map("ban_reason") // 封禁原因（管理员可选填写）
  balance      Decimal    @default(0) @db.Decimal(10, 2)
  avatarStyle  String     @default("bigSmile") @map("avatar_style") // bigSmile, croodles, notionists, lorelei
  avatarBadgeId String?   @map("avatar_badge_id")
  hasCreatedHostBefore Boolean @default(false) @map("has_created_host_before")

  // 2FA 双因素认证
  twoFactorEnabled       Boolean @default(false) @map("two_factor_enabled")
  twoFactorSecret        String? @map("two_factor_secret")
  twoFactorRecoveryCodes String? @map("two_factor_recovery_codes") // JSON array of encrypted codes

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

  // 关系
  quota                UserQuota?
  inviteCodes          InviteCode[]          @relation("CreatedInviteCodes")
  usedInviteCodes      InviteCode[]          @relation("UsedInviteCodes")
  sshKeys              SshKey[]
  instances            Instance[]
  notificationChannels NotificationChannel[]
  storageConfigs       StorageConfig[]
  oauthBindings        UserOAuthBinding[]
  telegramBinding      UserTelegramBinding?
  telegramBindTokens   TelegramBindToken[]
  helpArticles         HelpArticle[]         @relation("CreatedArticles")
  logs                 Log[]
  transfersSent        InstanceTransfer[]    @relation("TransferFrom")
  transfersReceived    InstanceTransfer[]    @relation("TransferTo")
  refreshTokens        RefreshToken[]
  tokenInvalidations   TokenInvalidation[]

  // 用户资源关系
  hosts    Host[]
  packages Package[]

  // 好友关系
  friendshipsSent     Friendship[] @relation("FriendshipSent")
  friendshipsReceived Friendship[] @relation("FriendshipReceived")

  // 套餐共享关系
  packageSharesOwned    PackageShare[] @relation("PackageShareOwner")
  packageSharesReceived PackageShare[] @relation("PackageShareReceiver")

  // 敏感操作验证
  operationVerifications OperationVerification[]

  // 站内信
  inboxMessages InboxMessage[]
  hostNotificationEmailTasks HostNotificationEmailTask[]

  // 登录记录
  loginRecords LoginRecord[]

  // 公告发送记录
  announcements Announcement[] @relation("AnnouncementSender")

  // 工单系统
  ticketsCreated   Ticket[]        @relation("TicketCreator")
  ticketMessages   TicketMessage[] @relation("TicketMessageSender")
  ticketAttachments TicketMessageAttachment[] @relation("TicketAttachmentUploader")

  // 签到系统
  checkinRecords   CheckinRecord[] @relation("CheckinRecords")
  redeemRecords    CheckinRecord[] @relation("RedeemRecords")
  checkinStats     CheckinStats?

  // 系统兑换码
  redeemCodesCreated RedeemCode[]      @relation("RedeemCodesCreated")
  redeemCodeUsages   RedeemCodeUsage[] @relation("RedeemCodeUsages")

  // 用户自定义初始化命令
  customInitCommands CustomInitCommand[]

  // 用户终端快捷命令
  terminalSavedCommands TerminalSavedCommand[]

  // 计费相关
  balanceLogs     BalanceLog[]
  rechargeRecords RechargeRecord[]
  badgeOwnerships UserBadgeOwnership[]
  vipBenefitClaims VipBenefitClaim[]

  // AFF推荐计划
  affBalance       Decimal         @default(0) @map("aff_balance") @db.Decimal(10, 2)  // AFF余额
  affActivatedAt   DateTime?       @map("aff_activated_at")                            // AFF激活时间
  affCodes         AffCode[]
  affLogs          AffLog[]
  affWithdrawals   AffWithdrawal[]

  // 托管余额系统
  hostingBalance      Decimal              @default(0) @map("hosting_balance") @db.Decimal(10, 2) // 可用托管余额
  hostingBalanceLogs  HostingBalanceLog[]
  hostingWithdrawals  HostingWithdrawal[]
  hostingBlocksCreated  HostingUserBlock[] @relation("HostingBlocker")
  hostingBlocksReceived HostingUserBlock[] @relation("HostingBlockedUser")
  hostingZone         HostingZone?         @relation("HostingZoneOwner")

  // 域名邮箱系统
  mailSubscriptions   MailSubscription[]

  // 抽奖系统
  userPoints       UserPoints?
  pointsLogs       PointsLog[]
  lotteryRecords   LotteryRecord[]

  // 资源池系统
  resourcePool     UserResourcePool?
  resourcePoolLogs ResourcePoolLog[]

  // 销毁记录
  destroyRecords   UserDestroyRecord[]
  instanceAuditRules InstanceAuditRule[] @relation("InstanceAuditRuleCreator")
  instanceAuditBuiltinRuleOverrides InstanceAuditBuiltinRuleOverride[] @relation("InstanceAuditBuiltinRuleOverrideCreator")
  instanceAuditIgnores InstanceAuditIgnore[] @relation("InstanceAuditIgnoreCreator")
  instanceAuditScans InstanceAuditScan[] @relation("InstanceAuditScanUser")
  instanceAuditActions InstanceAuditAction[] @relation("InstanceAuditActionUser")

  @@map("users")
}

model UserQuota {
  id     Int @id @default(autoincrement())
  userId Int @unique @map("user_id")

  // 新配额系统：控制用户可拥有的资源数量
  // 默认值为 0，表示功能未开放，需要管理员授权才能使用
  // 注意：不再限制实例配额，用户可以创建无限数量的实例
  hostLimit    Int @default(0) @map("host_limit") // 宿主机数量上限（0 = 未授权）
  hostUsed     Int @default(0) @map("host_used") // 已使用宿主机数量
  friendLimit  Int @default(0) @map("friend_limit") // 好友数量上限（0 = 未授权）
  friendUsed   Int @default(0) @map("friend_used") // 已使用好友数量
  packageLimit Int @default(0) @map("package_limit") // 套餐数量上限（0 = 未授权）
  packageUsed  Int @default(0) @map("package_used") // 已使用套餐数量

  // 流量控制 (单位: Bytes)
  monthlyTrafficLimit  BigInt?       @map("monthly_traffic_limit") // null = 无限制
  monthlyTrafficUsed   BigInt        @default(0) @map("monthly_traffic_used")
  trafficStatus        TrafficStatus @default(NORMAL) @map("traffic_status")
  trafficWarningSentAt DateTime?     @map("traffic_warning_sent_at")

  // 流量包扩展 (预留)
  extraTrafficQuota BigInt @default(0) @map("extra_traffic_quota")
  extraTrafficUsed  BigInt @default(0) @map("extra_traffic_used")

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

  @@map("user_quotas")
}

model InviteCode {
  id           Int       @id @default(autoincrement())
  code         String    @unique
  createdBy    Int       @map("created_by")
  usedBy       Int?      @map("used_by")
  usedAt       DateTime? @map("used_at")
  expiresAt    DateTime? @map("expires_at")
  costSnapshot Json?     @map("cost_snapshot")
  createdAt    DateTime  @default(now()) @map("created_at")

  creator User  @relation("CreatedInviteCodes", fields: [createdBy], references: [id])
  user    User? @relation("UsedInviteCodes", fields: [usedBy], references: [id])

  @@index([code])
  @@map("invite_codes")
}

model SshKey {
  id          Int      @id @default(autoincrement())
  userId      Int      @map("user_id")
  name        String
  publicKey   String   @map("public_key")
  fingerprint String
  createdAt   DateTime @default(now()) @map("created_at")

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

  @@index([userId])
  @@map("ssh_keys")
}

// ==================== 会话管理模型 ====================

model RefreshToken {
  id           Int      @id @default(autoincrement())
  token        String   @unique // Refresh Token 完整值
  userId       Int      @map("user_id")
  username     String
  role         UserRole
  ip           String? // 登录 IP
  userAgent    String?  @map("user_agent") // 用户代理
  createdAt    DateTime @default(now()) @map("created_at")
  expiresAt    DateTime @map("expires_at") // 过期时间
  lastActiveAt DateTime @default(now()) @updatedAt @map("last_active_at") // 最后活跃时间

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

  @@index([userId])
  @@index([token])
  @@index([expiresAt]) // 用于清理过期 token
  @@map("refresh_tokens")
}

// ==================== Token 失效标记模型 ====================

model TokenInvalidation {
  id            Int      @id @default(autoincrement())
  userId        Int      @map("user_id")
  sessionId     String?  @map("session_id") // 会话ID（Refresh Token 前缀），null 表示用户级别的失效
  invalidatedAt Int      @map("invalidated_at") // 失效时间戳（秒级，与 JWT iat 一致）
  createdAt     DateTime @default(now()) @map("created_at")

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

  @@unique([userId, sessionId]) // 每个用户每个会话只能有一条失效记录（sessionId 为 null 时表示用户级别）
  @@index([userId])
  @@index([sessionId])
  @@index([invalidatedAt]) // 用于清理过期记录
  @@map("token_invalidations")
}

// ==================== 好友关系模型 ====================

model Friendship {
  id         Int              @id @default(autoincrement())
  userId     Int              @map("user_id") // 发起者
  friendId   Int              @map("friend_id") // 接收者
  status     FriendshipStatus @default(pending)
  remark     String? // 申请备注
  createdAt  DateTime         @default(now()) @map("created_at")
  acceptedAt DateTime?        @map("accepted_at")
  rejectedAt DateTime?        @map("rejected_at")

  user   User @relation("FriendshipSent", fields: [userId], references: [id], onDelete: Cascade)
  friend User @relation("FriendshipReceived", fields: [friendId], references: [id], onDelete: Cascade)

  @@unique([userId, friendId])
  @@index([friendId, status])
  @@index([userId, status])
  @@map("friendships")
}

// ==================== 节点相关模型 ====================

model Host {
  id                  Int          @id @default(autoincrement())
  userId              Int          @map("user_id") // 所有者
  name                String
  url                 String
  location            String?
  countryCode         String       @default("us") @map("country_code")
  architecture        String       @default("x86_64")
  status              HostStatus   @default(offline)
  certPath            String?      @map("cert_path")
  keyPath             String?      @map("key_path")
  natPublicIp         String?      @map("nat_public_ip")
  natPublicIpv6       String?      @map("nat_public_ipv6") // 宿主机公网 IPv6（用于 nat_ipv6_nat / ipv6_nat 模式）
  natBindIp           String?      @map("nat_bind_ip") // 端口映射实际监听的 IPv4 地址
  natBindIpv6         String?      @map("nat_bind_ipv6") // 端口映射实际监听的 IPv6 地址
  natPortStart        Int?         @map("nat_port_start")
  natPortEnd          Int?         @map("nat_port_end")
  cpuUsed             Int          @default(0) @map("cpu_used")
  memoryUsed          Int          @default(0) @map("memory_used")
  diskUsed            Int          @default(0) @map("disk_used")
  tags                Json?        @default("[]")
  natPortsUsedCount   Int          @default(0) @map("nat_ports_used_count")
  cpuAllowanceMax     Int          @default(0) @map("cpu_allowance_max")
  memoryMax           Int          @default(0) @map("memory_max")
  instanceType        InstanceType @default(container) @map("instance_type")
  // 初始化配置字段
  ipAddress           String?      @map("ip_address")
  // 存储配置
  storageDriver       String       @default("zfs") @map("storage_driver") // zfs | lvm
  storageType         String       @default("loop") @map("storage_type") // loop | disk
  storagePath         String?      @map("storage_path") // 设备路径如 /dev/sdb，或 loop 模式下为空
  storageSize         Int          @default(60) @map("storage_size") // loop 模式下的大小 (GB)
  // 网络配置
  ipv6Mode            Int          @default(1) @map("ipv6_mode") // 1=Routed, 2=NAT, 3=Disabled
  ipv6Subnet          String?      @map("ipv6_subnet")
  ipv6Gateway         String?      @map("ipv6_gateway")
  ipv6ParentInterface String?      @map("ipv6_parent_interface") // 宿主机物理网卡名 (routed 模式)
  // API 与内核配置
  enableApi           Boolean      @default(true) @map("enable_api")
  sysctlConfig        String?      @map("sysctl_config") // 自定义内核参数
  // 安装流程
  installToken        String?      @unique @map("install_token")
  installTokenExpire  DateTime?    @map("install_token_expire")
  isInstalled         Boolean      @default(false) @map("is_installed")
  // 证书下载限制（每次安装操作独立计数）
  certDownloadCount   Int          @default(0) @map("cert_download_count")
  certDownloadExpire  DateTime?    @map("cert_download_expire")
  createdAt           DateTime     @default(now()) @map("created_at")
  updatedAt           DateTime     @updatedAt @map("updated_at")

  // Caddy 反代配置
  caddyEnabled  Boolean @default(false) @map("caddy_enabled") // 是否已安装 Caddy
  caddyUsername String? @map("caddy_username") // Basic Auth 用户名
  caddyPassword String? @map("caddy_password") // Basic Auth 密码
  caddyPort     Int     @default(8444) @map("caddy_port") // Caddy API 端口

  // 转移控制
  transferEnabled Boolean @default(true) @map("transfer_enabled") // 是否允许该宿主机下的实例进行转移

  // 流量配置
  trafficResetDay Int @default(1) @map("traffic_reset_day") // 流量重置日（1-28），默认每月1号

  // 节点公告
  announcement String? @map("announcement") // 节点公告内容，显示给该节点下的实例用户

  // 节点通知设置（仅托管主外部通知）
  notifyPurchase Boolean @default(true) @map("notify_purchase") // 是否通知新购
  notifyRenew    Boolean @default(true) @map("notify_renew") // 是否通知续费
  notifyDestroy  Boolean @default(false) @map("notify_destroy") // 是否通知销毁/退款

  // 资源池玩法
  enableResourcePool Boolean @default(true) @map("enable_resource_pool") // 是否参与资源池玩法（签到/抽奖资源应用）

  // 探针地址
  probeUrl String? @map("probe_url") // 节点探针监控地址

  user         User          @relation(fields: [userId], references: [id], onDelete: Cascade)
  instances    Instance[]
  ipAddresses  IpAddress[]
  allowedImages HostAllowedImage[]
  portMappings PortMapping[]
  packageHosts PackageHost[]
  storagePools StoragePool[]
  proxySites   ProxySite[]
  announcements Announcement[] @relation("HostAnnouncements")
  tickets       Ticket[]       @relation("HostTickets")
  redeemCodes   RedeemCode[]
  destroyRecords UserDestroyRecord[]
  hostNotificationEmailTasks HostNotificationEmailTask[]
  addressAliases         HostAddressAlias[]         @relation("HostAddressAliases")
  addressResolutionLogs  HostAddressResolutionLog[] @relation("HostAddressResolutionLogs")
  addressConflictsA      HostAddressConflict[]      @relation("HostAddressConflictA")
  addressConflictsB      HostAddressConflict[]      @relation("HostAddressConflictB")
  agent                  HostAgent?
  instanceAuditRules     InstanceAuditRule[]
  instanceAuditBuiltinRuleOverrides InstanceAuditBuiltinRuleOverride[]
  instanceAuditIgnores   InstanceAuditIgnore[]
  instanceAuditScans     InstanceAuditScan[]
  instanceAuditActions   InstanceAuditAction[]

  @@unique([userId, name]) // 同一用户下名称唯一
  @@index([userId])
  @@map("hosts")
}

model HostAgent {
  id                    Int       @id @default(autoincrement())
  hostId                Int       @unique @map("host_id")
  agentId               String    @unique @map("agent_id")
  secretHash            String    @map("secret_hash")
  secretEncrypted       String    @map("secret_encrypted")
  installTokenHash      String?   @unique @map("install_token_hash")
  installTokenExpiresAt DateTime? @map("install_token_expires_at")
  installTokenUsedAt    DateTime? @map("install_token_used_at")
  enabled               Boolean   @default(true)
  status                String    @default("offline")
  version               String?
  capabilities          Json?     @default("[]")
  lastReport            Json?     @default("{}") @map("last_report")
  lastSeenAt            DateTime? @map("last_seen_at")
  lastHeartbeatIp       String?   @map("last_heartbeat_ip")
  createdAt             DateTime  @default(now()) @map("created_at")
  updatedAt             DateTime  @updatedAt @map("updated_at")

  host   Host             @relation(fields: [hostId], references: [id], onDelete: Cascade)
  nonces HostAgentNonce[]

  @@index([enabled])
  @@index([status])
  @@index([lastSeenAt])
  @@index([installTokenExpiresAt])
  @@map("host_agents")
}

model HostAgentNonce {
  id        Int      @id @default(autoincrement())
  agentId   String   @map("agent_id")
  nonce     String
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  agent HostAgent @relation(fields: [agentId], references: [agentId], onDelete: Cascade)

  @@unique([agentId, nonce])
  @@index([agentId])
  @@index([expiresAt])
  @@map("host_agent_nonces")
}

model HostAddressAlias {
  id        Int               @id @default(autoincrement())
  hostId    Int               @map("host_id")
  address   String
  kind      HostAddressKind
  source    HostAddressSource
  createdAt DateTime          @default(now()) @map("created_at")
  updatedAt DateTime          @updatedAt @map("updated_at")

  host Host @relation("HostAddressAliases", fields: [hostId], references: [id], onDelete: Cascade)

  @@unique([hostId, address])
  @@index([hostId])
  @@index([address])
  @@index([source, kind])
  @@map("host_address_aliases")
}

model HostAddressResolutionLog {
  id                Int                    @id @default(autoincrement())
  hostId            Int                    @map("host_id")
  inputAddress      String                 @map("input_address")
  inputKind         HostAddressKind        @map("input_kind")
  trigger           HostAddressCheckTrigger
  status            HostAddressCheckStatus
  resolvedAddresses Json                   @default("[]") @map("resolved_addresses")
  error             String?
  details           Json?                  @default("{}")
  createdAt         DateTime               @default(now()) @map("created_at")

  host Host @relation("HostAddressResolutionLogs", fields: [hostId], references: [id], onDelete: Cascade)

  @@index([hostId, createdAt(sort: Desc)])
  @@index([status, createdAt(sort: Desc)])
  @@map("host_address_resolution_logs")
}

model HostAddressConflict {
  id              Int                       @id @default(autoincrement())
  hostAId         Int                       @map("host_a_id")
  hostBId         Int                       @map("host_b_id")
  address         String
  status          HostAddressConflictStatus @default(active)
  firstDetectedAt DateTime                  @default(now()) @map("first_detected_at")
  lastDetectedAt  DateTime                  @updatedAt @map("last_detected_at")
  resolvedAt      DateTime?                 @map("resolved_at")

  hostA Host @relation("HostAddressConflictA", fields: [hostAId], references: [id], onDelete: Cascade)
  hostB Host @relation("HostAddressConflictB", fields: [hostBId], references: [id], onDelete: Cascade)

  @@unique([hostAId, hostBId, address])
  @@index([address])
  @@index([status, lastDetectedAt(sort: Desc)])
  @@map("host_address_conflicts")
}

// 存储池模型
model StoragePool {
  id          Int            @id @default(autoincrement())
  hostId      Int            @map("host_id")
  name        String // 存储池名称（与 Incus 一致）
  driver      String // zfs | lvm | btrfs | dir
  purpose     StoragePurpose @default(instance_data)
  description String?
  config      Json?          @default("{}") // 存储原始配置
  createdAt   DateTime       @default(now()) @map("created_at")
  updatedAt   DateTime       @updatedAt @map("updated_at")

  host Host @relation(fields: [hostId], references: [id], onDelete: Cascade)

  @@unique([hostId, name]) // 同一宿主机下存储池名称唯一
  @@index([hostId])
  @@map("storage_pools")
}

// ==================== 套餐相关模型 ====================

// 套餐与宿主机的多对多关联表
model PackageHost {
  id        Int      @id @default(autoincrement())
  packageId Int      @map("package_id")
  hostId    Int      @map("host_id")
  storagePoolName String? @map("storage_pool_name")
  trafficMultiplier Decimal @default(1.0) @map("traffic_multiplier") @db.Decimal(8, 3)
  createdAt DateTime @default(now()) @map("created_at")

  package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
  host    Host    @relation(fields: [hostId], references: [id], onDelete: Cascade)

  @@unique([packageId, hostId]) // 防止重复绑定
  @@index([packageId])
  @@index([hostId])
  @@map("package_hosts")
}

model Package {
  id                  Int          @id @default(autoincrement())
  userId              Int          @map("user_id") // 所有者
  name                String
  description         String?
  cpuMax              Int          @map("cpu_max")
  memoryMax           Int          @map("memory_max")
  diskMax             Int          @map("disk_max")
  bandwidthMax        Int?         @map("bandwidth_max")
  networkMode         NetworkMode  @default(nat) @map("network_mode")
  instanceType        InstanceType @default(container) @map("instance_type") // 实例类型: 容器/虚拟机
  nodeSelectors       Json?        @default("[]") @map("node_selectors")
  privileged          Boolean      @default(false)
  nested              Boolean      @default(false)
  active              Boolean      @default(true)
  // 套餐资源限制
  portLimit           Int          @default(20) @map("port_limit") // 端口映射数上限
  snapshotLimit       Int          @default(5) @map("snapshot_limit") // 快照数上限
  backupLimit         Int          @default(3) @map("backup_limit") // 备份数上限
  siteLimit           Int          @default(10) @map("site_limit") // 反代站点数上限
  monthlyTrafficLimit BigInt?      @map("monthly_traffic_limit") // 月流量限额 (Bytes)，null = 无限制

  // 存储 I/O 限制
  ioLimitMode     String @default("throughput") @map("io_limit_mode") // "throughput" | "iops" - IO限制模式
  limitsRead      String @default("100MB") @map("limits_read") // 磁盘读取吞吐限制
  limitsWrite     String @default("100MB") @map("limits_write") // 磁盘写入吞吐限制
  limitsReadIops  Int    @default(500) @map("limits_read_iops") // 磁盘读取 IOPS
  limitsWriteIops Int    @default(500) @map("limits_write_iops") // 磁盘写入 IOPS


  // 网络限制
  limitsIngress String @default("300Mbit") @map("limits_ingress") // 网络入站带宽
  limitsEgress  String @default("300Mbit") @map("limits_egress") // 网络出站带宽

  // 进程与调度
  limitsProcesses   Int @default(500) @map("limits_processes") // 进程数限制
  limitsCpuPriority Int @default(10) @map("limits_cpu_priority") // CPU 调度优先级 (0-10)

  // 启动配置
  bootAutostart           Boolean @default(true) @map("boot_autostart") // 开机自启
  bootAutostartPriority   Int     @default(20) @map("boot_autostart_priority") // 启动优先级 (0-100)
  bootAutostartDelay      Int     @default(15) @map("boot_autostart_delay") // 启动延迟 (5-600秒)
  bootHostShutdownTimeout Int     @default(30) @map("boot_host_shutdown_timeout") // 关机超时 (30-600秒)

  // 全局共享配置（当 globalShared = true 时，所有用户都可以使用此套餐）
  globalShared          Boolean  @default(false) @map("global_shared") // 是否全局共享
  globalQuotaMultiplier Decimal? @map("global_quota_multiplier") @db.Decimal(3, 1) // 全局共享的配额倍数限制，如 0.5, 1.0, 1.5, 2.0，null 表示无限制（1x）
  globalMaxInstances    Int?     @map("global_max_instances") // 全局共享的最大实例数，null 表示无限制

  // 开通门槛：创建本套餐实例前，用户必须已持有指定前置套餐的正常实例
  requiredPackageId Int? @map("required_package_id")

  // 实例操作权限
  allowInstanceDeletion Boolean @default(true) @map("allow_instance_deletion") // 是否允许用户删除实例

  // 资源释放通知渠道（可选）
  releaseChannelId Int? @map("release_channel_id")

  createdAt DateTime @default(now()) @map("created_at")

  user           User                 @relation(fields: [userId], references: [id], onDelete: Cascade)
  instances      Instance[]
  shares         PackageShare[]
  packageHosts   PackageHost[]
  plans          PackagePlan[]        // 套餐下的付费方案
  requiredPackage  Package?  @relation("PackagePrerequisite", fields: [requiredPackageId], references: [id], onDelete: NoAction)
  dependentPackages Package[] @relation("PackagePrerequisite")
  releaseChannel NotificationChannel? @relation("PackageReleaseChannel", fields: [releaseChannelId], references: [id], onDelete: SetNull)

  @@unique([userId, name]) // 同一用户下名称唯一
  @@index([userId])
  @@index([requiredPackageId])
  @@map("packages")
}

// ==================== 实例相关模型 ====================

model Instance {
  id               Int            @id @default(autoincrement())
  incusId          String         @map("incus_id")
  name             String
  userId           Int            @map("user_id")
  hostId           Int            @map("host_id")
  packageId        Int?           @map("package_id")
  packagePlanId    Int?           @map("package_plan_id")  // 套餐方案 ID：null 表示免费实例
  storagePoolName  String?        @map("storage_pool_name")
  image            String
  status           InstanceStatus @default(creating)
  cpu              Int
  memory           Int
  disk             Int
  ipv4             String?
  ipv6             String?
  networkMode      NetworkMode    @default(nat) @map("network_mode")
  sshPort          Int?           @map("ssh_port")
  rootPassword     String?        @map("root_password")
  snapshottedSpecs Json?          @default("{}") @map("snapshotted_specs")
  portLimit        Int?           @map("port_limit")
  snapshotLimit    Int?           @map("snapshot_limit")
  backupLimit      Int?           @map("backup_limit")
  siteLimit        Int?           @map("site_limit")
  swapEnabled      Boolean        @default(false) @map("swap_enabled")
  swapSize         Int?           @map("swap_size") // SWAP 大小(MB)，null/0 表示未配置

  // 实例级流量控制
  monthlyTrafficLimit BigInt?       @map("monthly_traffic_limit")
  monthlyTrafficUsed  BigInt        @default(0) @map("monthly_traffic_used")
  trafficStatus       TrafficStatus @default(NORMAL) @map("traffic_status")

  // 存储 I/O 限制 (null = 继承套餐)
  limitsRead      String? @map("limits_read")
  limitsWrite     String? @map("limits_write")
  limitsReadIops  Int?    @map("limits_read_iops")
  limitsWriteIops Int?    @map("limits_write_iops")

  // 网络限制
  limitsIngress String? @map("limits_ingress")
  limitsEgress  String? @map("limits_egress")

  // 进程与调度
  limitsProcesses   Int? @map("limits_processes")
  limitsCpuPriority Int? @map("limits_cpu_priority")

  // 启动配置
  bootAutostart           Boolean? @map("boot_autostart")
  bootAutostartPriority   Int?     @map("boot_autostart_priority")
  bootAutostartDelay      Int?     @map("boot_autostart_delay")
  bootHostShutdownTimeout Int?     @map("boot_host_shutdown_timeout")

  // 恢复来源信息
  restoredFrom Json? @map("restored_from") // { backupId, backupName, restoredAt }

  // ========== 计费与封停相关字段 ==========
  // 到期时间：null 表示永不到期（免费实例），有值表示付费实例的到期时间
  expiresAt        DateTime? @map("expires_at")
  // 封停信息
  suspendedAt      DateTime? @map("suspended_at")      // 封停时间
  suspendedBy      Int?      @map("suspended_by")      // 封停操作者（null=系统自动，有值=用户ID）
  suspendReason    String?   @map("suspend_reason")    // 封停原因（expired=到期封停，其他=手动封停原因）
  // 计费价格（从方案复制，支持后续调整）
  billingPrice     Decimal?  @map("billing_price") @db.Decimal(10, 2)  // 单周期价格
  billingCycle     Int?      @map("billing_cycle")  // 计费周期（月）
  // 自动续费
  autoRenew        Boolean   @default(false) @map("auto_renew")
  iconBadgeId      String?   @map("icon_badge_id")
  autoRenewAttempts     Int        @default(0) @map("auto_renew_attempts")      // 已尝试次数
  lastAutoRenewAttemptAt DateTime? @map("last_auto_renew_attempt_at")           // 上次尝试时间
  // 到期提醒记录
  expiryNotifiedAt DateTime? @map("expiry_notified_at") // 上次发送到期提醒时间
  // 乐观锁
  version          Int       @default(0)  // 版本号，用于并发控制
  // IPv6 重新分配冷却
  lastIpv6ReassignAt DateTime? @map("last_ipv6_reassign_at") // 上次重新分配 IPv6 时间（每实例每天限一次）
  // 方案变更冷却
  lastPlanChangeAt   DateTime? @map("last_plan_change_at")   // 上次变更方案时间（3天冷却期）
  // 用户自定义显示顺序；相同顺序时按创建时间倒序
  displayOrder       Int       @default(0) @map("display_order")

  // Cloud-init 检测状态
  cloudInitState             String?   @map("cloud_init_state")
  cloudInitSource            String?   @map("cloud_init_source")
  cloudInitLastCheckedAt     DateTime? @map("cloud_init_last_checked_at")
  cloudInitCompletedAt       DateTime? @map("cloud_init_completed_at")
  cloudInitManualCompletedAt DateTime? @map("cloud_init_manual_completed_at")
  cloudInitManualCompletedBy Int?      @map("cloud_init_manual_completed_by")

  createdAt    DateTime  @default(now()) @map("created_at")
  updatedAt    DateTime  @updatedAt @map("updated_at")
  lastSyncedAt DateTime? @map("last_synced_at") // 最后与 Incus 同步时间

  user            User               @relation(fields: [userId], references: [id])
  host            Host               @relation(fields: [hostId], references: [id])
  package         Package?           @relation(fields: [packageId], references: [id], onDelete: SetNull)
  packagePlan     PackagePlan?       @relation(fields: [packagePlanId], references: [id], onDelete: SetNull)
  portMappings    PortMapping[]
  snapshots       Snapshot[]
  backups         Backup[]
  backupPolicy    BackupPolicy?
  snapshotPolicy  SnapshotPolicy?
  trafficSnapshot TrafficSnapshot?
  dailyTraffic    DailyTraffic[]
  transfers       InstanceTransfer[]
  ipAddresses     IpAddress[]
  ipv6Subnets     Ipv6Subnet[]
  proxySites       ProxySite[]
  instanceTasks    InstanceTask[]
  tickets          Ticket[]          @relation("InstanceTickets")
  checkinRecords   CheckinRecord[]
  redeemCodeUsages RedeemCodeUsage[]
  billingRecords   InstanceBillingRecord[]  // 计费记录
  affBinding       AffBinding?              // AFF优惠码绑定
  resourcePoolLogs ResourcePoolLog[]        // 资源池应用记录
  destroyRecords   UserDestroyRecord[]      // 销毁记录
  appliedBadgeOwnerships UserBadgeOwnership[] @relation("InstanceAppliedBadgeOwnerships")
  logs              Log[]
  auditIgnores     InstanceAuditIgnore[]
  auditScans       InstanceAuditScan[]
  auditActions     InstanceAuditAction[]

  @@index([userId])
  @@index([hostId])
  @@index([status])
  @@index([hostId, status]) // 高可用优化：状态同步调度器使用
  @@index([expiresAt])      // 计费调度器使用：查找即将到期的实例
  @@index([userId, displayOrder, createdAt(sort: Desc)])
  @@map("instances")
}

// IP 地址类型枚举
enum IpType {
  inet4
  inet6
}

// IP 地址池模型 (支持多 IP)
model IpAddress {
  id        Int     @id @default(autoincrement())
  address   String
  type      IpType  @default(inet6)
  isPrimary Boolean @default(false) @map("is_primary")
  isCustom  Boolean @default(false) @map("is_custom") // 用户自定义 IP
  device    String  @default("eth1") // 网卡名: eth0(NAT), eth1(routed IPv6)

  hostId Int  @map("host_id")
  host   Host @relation(fields: [hostId], references: [id], onDelete: Cascade)

  instanceId Int      @map("instance_id")
  instance   Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  createdAt DateTime @default(now()) @map("created_at")

  @@index([address])
  @@index([hostId])
  @@index([hostId, address])
  @@index([instanceId])
  @@map("ip_addresses")
}

// IPv6 网段分配模型
model Ipv6Subnet {
  id        Int    @id @default(autoincrement())
  cidr      String @unique // 网段 CIDR，如 "2a01:4f9:c012:e7:aaaa::/112"
  primaryIp String @map("primary_ip") // 网段主 IP (通常是 ::1)
  device    String @default("eth1")

  instanceId Int      @map("instance_id")
  instance   Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  createdAt DateTime @default(now()) @map("created_at")

  @@index([instanceId])
  @@map("ipv6_subnets")
}

model PortMapping {
  id          Int      @id @default(autoincrement())
  instanceId  Int      @map("instance_id")
  hostId      Int      @map("host_id")
  protocol    Protocol @default(tcp)
  publicPort  Int      @map("public_port")
  privatePort Int      @map("private_port")
  remark      String?
  createdAt   DateTime @default(now()) @map("created_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  host     Host     @relation(fields: [hostId], references: [id])

  @@unique([hostId, protocol, publicPort])
  @@index([instanceId])
  @@map("port_mappings")
}

// ==================== 快照/备份相关模型 ====================

model Snapshot {
  id          Int      @id @default(autoincrement())
  instanceId  Int      @map("instance_id")
  incusName   String   @map("incus_name")
  name        String
  description String?
  stateful    Boolean  @default(false)
  size        Int      @default(0)
  createdAt   DateTime @default(now()) @map("created_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([instanceId])
  @@map("snapshots")
}

model Backup {
  id          Int          @id @default(autoincrement())
  instanceId  Int          @map("instance_id")
  incusName   String       @map("incus_name")
  name        String
  description String?
  size        Int          @default(0)
  status      BackupStatus @default(creating)
  createdAt   DateTime     @default(now()) @map("created_at")
  expiresAt   DateTime?    @map("expires_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([instanceId])
  @@index([status])
  @@map("backups")
}

model BackupPolicy {
  id              Int       @id @default(autoincrement())
  instanceId      Int       @unique @map("instance_id")
  enabled         Boolean   @default(false)
  intervalMinutes Int       @default(1440) @map("interval_minutes") // 默认24小时
  lastRunAt       DateTime? @map("last_run_at")
  nextRunAt       DateTime? @map("next_run_at")
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([enabled, nextRunAt]) // 高可用优化：自动策略调度器使用
  @@map("backup_policies")
}

model SnapshotPolicy {
  id              Int       @id @default(autoincrement())
  instanceId      Int       @unique @map("instance_id")
  enabled         Boolean   @default(false)
  intervalMinutes Int       @default(360) @map("interval_minutes") // 默认6小时
  lastRunAt       DateTime? @map("last_run_at")
  nextRunAt       DateTime? @map("next_run_at")
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([enabled, nextRunAt]) // 高可用优化：自动策略调度器使用
  @@map("snapshot_policies")
}

// ==================== 通知相关模型 ====================

model NotificationChannel {
  id        Int                     @id @default(autoincrement())
  userId    Int                     @map("user_id")
  type      NotificationChannelType
  name      String
  config    Json
  enabled   Boolean                 @default(true)
  isGlobal  Boolean                 @default(false) @map("is_global") // 管理员创建的全局渠道，对所有托管用户开放
  createdAt DateTime                @default(now()) @map("created_at")

  user     User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  logs     NotificationLog[]
  packages Package[]         @relation("PackageReleaseChannel") // 套餐资源释放通知

  @@index([userId])
  @@map("notification_channels")
}

model NotificationLog {
  id        Int                   @id @default(autoincrement())
  channelId Int                   @map("channel_id")
  eventType String                @map("event_type")
  message   String
  status    NotificationLogStatus
  error     String?
  createdAt DateTime              @default(now()) @map("created_at")

  channel NotificationChannel @relation(fields: [channelId], references: [id], onDelete: Cascade)

  @@map("notification_logs")
}

// ==================== OAuth 相关模型 ====================

model OAuthConfig {
  id           Int           @id @default(autoincrement())
  provider     OAuthProvider @unique
  clientId     String        @map("client_id")
  clientSecret String        @map("client_secret")
  enabled      Boolean       @default(false)
  createdAt    DateTime      @default(now()) @map("created_at")
  updatedAt    DateTime      @updatedAt @map("updated_at")

  @@map("oauth_configs")
}

model UserOAuthBinding {
  id               Int           @id @default(autoincrement())
  userId           Int           @map("user_id")
  provider         OAuthProvider
  providerUserId   String        @map("provider_user_id")
  providerUsername String?       @map("provider_username")
  providerEmail    String?       @map("provider_email")
  providerAvatar   String?       @map("provider_avatar")
  accessToken      String?       @map("access_token")
  refreshToken     String?       @map("refresh_token")
  createdAt        DateTime      @default(now()) @map("created_at")
  updatedAt        DateTime      @updatedAt @map("updated_at")

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

  @@unique([userId, provider])
  @@unique([provider, providerUserId])
  @@index([userId])
  @@index([provider, providerUserId])
  @@map("user_oauth_bindings")
}

model UserTelegramBinding {
  id               Int      @id @default(autoincrement())
  userId           Int      @unique @map("user_id")
  telegramUserId   String   @unique @map("telegram_user_id")
  telegramUsername String?  @map("telegram_username")
  firstName        String?  @map("first_name")
  lastName         String?  @map("last_name")
  boundAt          DateTime @default(now()) @map("bound_at")
  updatedAt        DateTime @updatedAt @map("updated_at")

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

  @@map("user_telegram_bindings")
}

model TelegramBindToken {
  id        Int       @id @default(autoincrement())
  userId    Int       @map("user_id")
  tokenHash String    @unique @map("token_hash")
  expiresAt DateTime  @map("expires_at")
  usedAt    DateTime? @map("used_at")
  createdAt DateTime  @default(now()) @map("created_at")

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

  @@index([userId])
  @@map("telegram_bind_tokens")
}

// ==================== 帮助文档模型 ====================

model HelpArticle {
  id        Int      @id @default(autoincrement())
  title     String
  slug      String   @unique
  content   String
  category  String   @default("general")
  sortOrder Int      @default(0) @map("sort_order")
  published Boolean  @default(true)
  pinned    Boolean  @default(false) // 是否置顶，置顶文章会显示在用户后台首页
  createdBy Int?     @map("created_by")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  creator User? @relation("CreatedArticles", fields: [createdBy], references: [id], onDelete: SetNull)

  @@map("help_articles")
}

// ==================== 系统配置模型 ====================

model SystemConfig {
  id          Int      @id @default(autoincrement())
  key         String   @unique
  value       String
  type        String   @default("string") // string, number, boolean, json
  label       String?
  description String?
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

  @@map("system_configs")
}

// ==================== 流量统计模型 ====================

// 流量快照 (用于增量计算)
model TrafficSnapshot {
  id         Int      @id @default(autoincrement())
  instanceId Int      @unique @map("instance_id")
  rxRaw      BigInt   @map("rx_raw") // 上次采集的原始值
  txRaw      BigInt   @map("tx_raw")
  updatedAt  DateTime @updatedAt @map("updated_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@map("traffic_snapshots")
}

// 每日流量聚合 (用于图表)
model DailyTraffic {
  id         Int      @id @default(autoincrement())
  instanceId Int      @map("instance_id")
  date       DateTime @db.Date // 只存日期
  rxTotal    BigInt   @map("rx_total")
  txTotal    BigInt   @map("tx_total")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@unique([instanceId, date])
  @@index([instanceId, date])
  @@map("daily_traffic")
}

// ==================== 远程存储配置模型 ====================

model StorageConfig {
  id     Int         @id @default(autoincrement())
  userId Int         @map("user_id")
  name   String // e.g. "我的群晖 NAS"
  type   StorageType

  // 连接信息
  host     String
  port     Int?
  username String?
  password String? // ★ 注意：写入前必须 AES 加密，读取时解密
  basePath String? @map("base_path") // e.g. "/backups/"

  // 扩展配置 (预留 S3 bucket/region/accessKey/secretKey 等)
  extra Json?

  isDefault Boolean  @default(false) @map("is_default")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@index([userId])
  @@map("storage_configs")
}

// ==================== 备份上传任务模型 ====================

model BackupUploadTask {
  id              Int                    @id @default(autoincrement())
  userId          Int                    @map("user_id")
  instanceId      Int                    @map("instance_id")
  backupId        Int                    @map("backup_id")
  hostId          Int                    @map("host_id")
  storageConfigId Int                    @map("storage_config_id")
  status          BackupUploadTaskStatus @default(PENDING)
  progress        String? // 当前执行步骤: preparing, uploading, finalizing
  retryCount      Int                    @default(0) @map("retry_count") // 重试次数

  // 上传结果
  remoteFileName String? @map("remote_file_name")
  fileSize       BigInt? @map("file_size")
  error          String?

  createdAt  DateTime  @default(now()) @map("created_at")
  startedAt  DateTime? @map("started_at")
  finishedAt DateTime? @map("finished_at")

  storageConfig StorageConfig @relation(fields: [storageConfigId], references: [id], onDelete: Cascade)

  @@index([hostId, status])
  @@index([userId])
  @@index([instanceId])
  @@index([storageConfigId, status])
  @@map("backup_upload_tasks")
}

// ==================== 日志模型 ====================

model Log {
  id         Int      @id @default(autoincrement())
  userId     Int?     @map("user_id")
  instanceId Int?     @map("instance_id")
  module     String
  action     String
  content    String
  result     String
  createdAt  DateTime @default(now()) @map("created_at")

  user     User?     @relation(fields: [userId], references: [id], onDelete: SetNull)
  instance Instance? @relation(fields: [instanceId], references: [id], onDelete: SetNull)

  @@index([userId])
  @@index([instanceId])
  @@index([module])
  @@index([createdAt])
  @@map("logs")
}

model InstanceAuditRule {
  id             Int      @id @default(autoincrement())
  hostId         Int?     @map("host_id")
  createdById    Int      @map("created_by_id")
  name           String
  description    String?
  severity       String   @default("medium")
  category       String   @default("custom")
  targetTypes    Json     @default("[]") @map("target_types")
  matchType      String   @default("contains") @map("match_type")
  pattern        String   @db.Text
  caseSensitive  Boolean  @default(false) @map("case_sensitive")
  recommendation String?  @db.Text
  enabled        Boolean  @default(true)
  createdAt      DateTime @default(now()) @map("created_at")
  updatedAt      DateTime @updatedAt @map("updated_at")

  host      Host? @relation(fields: [hostId], references: [id], onDelete: Cascade)
  createdBy User  @relation("InstanceAuditRuleCreator", fields: [createdById], references: [id], onDelete: Cascade)

  @@index([hostId, enabled])
  @@index([createdById])
  @@map("instance_audit_rules")
}

model InstanceAuditBuiltinRuleOverride {
  id             Int      @id @default(autoincrement())
  hostId         Int      @map("host_id")
  builtinRuleId  String   @map("builtin_rule_id")
  createdById    Int      @map("created_by_id")
  name           String
  description    String?
  severity       String   @default("medium")
  category       String   @default("custom")
  targetTypes    Json     @default("[]") @map("target_types")
  matchType      String   @default("contains") @map("match_type")
  pattern        String   @db.Text
  caseSensitive  Boolean  @default(false) @map("case_sensitive")
  recommendation String?  @db.Text
  enabled        Boolean  @default(true)
  createdAt      DateTime @default(now()) @map("created_at")
  updatedAt      DateTime @updatedAt @map("updated_at")

  host      Host @relation(fields: [hostId], references: [id], onDelete: Cascade)
  createdBy User @relation("InstanceAuditBuiltinRuleOverrideCreator", fields: [createdById], references: [id], onDelete: Cascade)

  @@unique([hostId, builtinRuleId])
  @@index([hostId, enabled])
  @@index([createdById])
  @@map("instance_audit_builtin_rule_overrides")
}

model InstanceAuditIgnore {
  id          Int       @id @default(autoincrement())
  hostId      Int       @map("host_id")
  instanceId  Int?      @map("instance_id")
  createdById Int       @map("created_by_id")
  ruleId      String?   @map("rule_id")
  targetType  String?   @map("target_type")
  matchText   String?   @map("match_text")
  scope       String    @default("instance")
  reason      String?   @db.Text
  enabled     Boolean   @default(true)
  expiresAt   DateTime? @map("expires_at")
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  host      Host      @relation(fields: [hostId], references: [id], onDelete: Cascade)
  instance  Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  createdBy User      @relation("InstanceAuditIgnoreCreator", fields: [createdById], references: [id], onDelete: Cascade)

  @@index([hostId, enabled])
  @@index([instanceId])
  @@index([ruleId])
  @@map("instance_audit_ignores")
}

model InstanceAuditScan {
  id               Int      @id @default(autoincrement())
  hostId           Int      @map("host_id")
  instanceId       Int      @map("instance_id")
  userId           Int      @map("user_id")
  status           String   @default("success")
  capability       String?
  riskLevel        String   @map("risk_level")
  findingCount     Int      @default(0) @map("finding_count")
  ignoredCount     Int      @default(0) @map("ignored_count")
  processCount     Int      @default(0) @map("process_count")
  connectionCount  Int      @default(0) @map("connection_count")
  listeningCount   Int      @default(0) @map("listening_count")
  startupItemCount Int      @default(0) @map("startup_item_count")
  findings         Json     @default("[]")
  error            String?  @db.Text
  createdAt        DateTime @default(now()) @map("created_at")

  host     Host     @relation(fields: [hostId], references: [id], onDelete: Cascade)
  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  user     User     @relation("InstanceAuditScanUser", fields: [userId], references: [id], onDelete: Cascade)
  actions  InstanceAuditAction[]

  @@index([hostId, createdAt(sort: Desc)])
  @@index([instanceId, createdAt(sort: Desc)])
  @@map("instance_audit_scans")
}

model InstanceAuditAction {
  id             Int      @id @default(autoincrement())
  scanId         Int?     @map("scan_id")
  hostId         Int      @map("host_id")
  instanceId     Int      @map("instance_id")
  userId         Int      @map("user_id")
  actionType     String   @map("action_type")
  pid            Int?
  signal         String?
  processCommand String?  @map("process_command") @db.Text
  reason         String   @db.Text
  result         String
  stdout         String?  @db.Text
  stderr         String?  @db.Text
  createdAt      DateTime @default(now()) @map("created_at")

  scan     InstanceAuditScan? @relation(fields: [scanId], references: [id], onDelete: SetNull)
  host     Host      @relation(fields: [hostId], references: [id], onDelete: Cascade)
  instance Instance  @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  user     User      @relation("InstanceAuditActionUser", fields: [userId], references: [id], onDelete: Cascade)

  @@index([hostId, createdAt(sort: Desc)])
  @@index([instanceId, createdAt(sort: Desc)])
  @@map("instance_audit_actions")
}

// ==================== 邮件验证码模型 ====================

model EmailVerificationCode {
  id        Int      @id @default(autoincrement())
  email     String
  code      String
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  @@index([email, code])
  @@index([expiresAt])
  @@map("email_verification_codes")
}

// ==================== 套餐共享模型 ====================

model PackageShare {
  id              Int      @id @default(autoincrement())
  packageId       Int      @map("package_id") // 被共享的套餐
  ownerId         Int      @map("owner_id") // 套餐所有者
  sharedToId      Int      @map("shared_to_id") // 共享给谁（必须是好友）
  quotaMultiplier Decimal? @map("quota_multiplier") @db.Decimal(3, 1) // 配额倍数限制，如 0.5, 1.0, 1.5, 2.0，null 表示无限制
  maxInstances    Int?     @map("max_instances") // 最多可开实例数，null 表示无限制
  createdAt       DateTime @default(now()) @map("created_at")

  package  Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
  owner    User    @relation("PackageShareOwner", fields: [ownerId], references: [id], onDelete: Cascade)
  sharedTo User    @relation("PackageShareReceiver", fields: [sharedToId], references: [id], onDelete: Cascade)

  @@unique([packageId, sharedToId]) // 同一套餐不能重复共享给同一人
  @@index([sharedToId])
  @@index([ownerId])
  @@index([packageId])
  @@map("package_shares")
}

// ==================== 实例转移模型 ====================

enum TransferStatus {
  pending // 等待接收
  processing // 处理中（临时锁定状态，防止并发）
  accepted // 已接收
  rejected // 已拒绝
  cancelled // 已取消
}

model InstanceTransfer {
  id           Int            @id @default(autoincrement())
  instanceId   Int            @map("instance_id")
  fromUserId   Int            @map("from_user_id")
  toUserId     Int            @map("to_user_id")
  status       TransferStatus @default(pending)
  snapshot     Json // 转移时的实例快照信息
  remark       String? // 转移备注
  rejectReason String?        @map("reject_reason")
  fee          Decimal?       @db.Decimal(10, 2) // 转移手续费（元），拒绝时退还
  createdAt    DateTime       @default(now()) @map("created_at")
  updatedAt    DateTime       @updatedAt @map("updated_at") // 状态变更时自动更新，用于超时检测
  acceptedAt   DateTime?      @map("accepted_at")
  rejectedAt   DateTime?      @map("rejected_at")
  cancelledAt  DateTime?      @map("cancelled_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  fromUser User     @relation("TransferFrom", fields: [fromUserId], references: [id])
  toUser   User     @relation("TransferTo", fields: [toUserId], references: [id])

  @@index([fromUserId, status])
  @@index([toUserId, status])
  @@index([instanceId, status])
  @@map("instance_transfers")
}

// ==================== 敏感操作二次验证模型 ====================

enum OperationType {
  // 账号相关操作（邮件验证）
  change_password
  disable_2fa
  change_email
  delete_account
  // 资源相关操作（通知渠道验证）
  delete_instance
  reinstall_instance
  recreate_instance  // 重建实例
  transfer_instance
  delete_snapshot
  delete_backup
}

enum VerificationChannel {
  email
  telegram
  discord
  webhook
}

model OperationVerification {
  id            Int                 @id @default(autoincrement())
  userId        Int                 @map("user_id")
  operationType OperationType       @map("operation_type")
  code          String // 6位验证码
  channel       VerificationChannel // 验证渠道
  resourceId    Int?                @map("resource_id") // 资源ID（如实例ID、快照ID等）
  resourceType  String?             @map("resource_type") // 资源类型（instance/snapshot/backup）
  verified      Boolean             @default(false)
  verifiedAt    DateTime?           @map("verified_at")
  expiresAt     DateTime            @map("expires_at")
  createdAt     DateTime            @default(now()) @map("created_at")

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

  @@index([userId, operationType, code])
  @@index([expiresAt])
  @@map("operation_verifications")
}

// ==================== 分布式锁模型 ====================
// LOGIC004: 支持多实例部署的分布式锁

model DistributedLock {
  id         Int      @id @default(autoincrement())
  lockKey    String   @unique @map("lock_key") // 锁的唯一标识
  ownerId    String   @map("owner_id") // 锁持有者ID（进程ID+随机字符串）
  acquiredAt DateTime @map("acquired_at") // 获取时间
  expiresAt  DateTime @map("expires_at") // 过期时间

  @@index([expiresAt]) // 用于清理过期锁
  @@map("distributed_locks")
}

// ==================== 系统镜像模型 ====================

model SystemImage {
  id           Int      @id @default(autoincrement())
  name         String // 显示名称，如 "Ubuntu 24.04 LTS"
  remoteAlias  String   @unique @map("remote_alias") // Incus 别名，如 "ubuntu/noble/cloud"
  osType       String   @default("Linux") @map("os_type")
  architecture String   @default("x86_64")
  instanceType String   @default("both") @map("instance_type") // 支持的实例类型: container, vm, both
  icon         String // 图标标识，如 "ubuntu"
  sortOrder    Int      @default(0) @map("sort_order")
  hidden       Boolean  @default(false) // 是否隐藏
  createdAt    DateTime @default(now()) @map("created_at")
  updatedAt    DateTime @updatedAt @map("updated_at")
  allowedHosts HostAllowedImage[]

  @@map("system_images")
}

model HostAllowedImage {
  id        Int         @id @default(autoincrement())
  hostId    Int         @map("host_id")
  imageId   Int         @map("image_id")
  createdAt DateTime    @default(now()) @map("created_at")
  updatedAt DateTime    @updatedAt @map("updated_at")
  host      Host        @relation(fields: [hostId], references: [id], onDelete: Cascade)
  image     SystemImage @relation(fields: [imageId], references: [id], onDelete: Cascade)

  @@unique([hostId, imageId])
  @@index([hostId])
  @@index([imageId])
  @@map("host_allowed_images")
}

// ==================== 公告/通知历史记录模型 ====================

enum AnnouncementType {
  system_broadcast // 管理员全站公告
  host_broadcast   // 宿主机节点公告
  admin_message    // 管理员私信
  host_message     // 宿主机所有者私信
}

model Announcement {
  id             Int              @id @default(autoincrement())
  type           AnnouncementType // 公告类型
  senderId       Int              @map("sender_id") // 发送者 ID
  title          String           // 标题
  content        String           // 内容
  recipientCount Int              @map("recipient_count") // 发送人数

  // 可选关联（根据类型）
  hostId       Int? @map("host_id") // 节点公告时的节点 ID
  targetUserId Int? @map("target_user_id") // 私信时的目标用户
  instanceId   Int? @map("instance_id") // 实例私信时的实例 ID

  createdAt DateTime @default(now()) @map("created_at")

  sender User @relation("AnnouncementSender", fields: [senderId], references: [id], onDelete: Cascade)
  host   Host? @relation("HostAnnouncements", fields: [hostId], references: [id], onDelete: SetNull)

  @@index([senderId, createdAt(sort: Desc)])
  @@index([hostId, createdAt(sort: Desc)])
  @@index([type, createdAt(sort: Desc)])
  @@map("announcements")
}

// ==================== 站内信模型 ====================

model InboxMessage {
  id        Int      @id @default(autoincrement())
  userId    Int      @map("user_id")
  eventType String   @map("event_type") // 事件类型，与 notifier.ts 一致
  title     String                       // 消息标题
  content   String                       // 消息内容
  isRead    Boolean  @default(false) @map("is_read")
  data      Json?                        // 原始事件数据，用于前端跳转
  createdAt DateTime @default(now()) @map("created_at")

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

  @@index([userId, isRead])
  @@index([userId, createdAt(sort: Desc)])
  @@map("inbox_messages")
}

model HostNotificationEmailTask {
  id          Int                             @id @default(autoincrement())
  userId      Int                             @map("user_id")
  hostId      Int                             @map("host_id")
  email       String
  username    String
  hostName    String                          @map("host_name")
  title       String
  content     String
  status      HostNotificationEmailTaskStatus @default(PENDING)
  retryCount  Int                             @default(0) @map("retry_count")
  lastError   String?                         @map("last_error")
  scheduledFor DateTime                       @map("scheduled_for")
  createdAt   DateTime                        @default(now()) @map("created_at")
  startedAt   DateTime?                       @map("started_at")
  finishedAt  DateTime?                       @map("finished_at")

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

  @@index([status, scheduledFor])
  @@index([userId])
  @@index([hostId])
  @@index([startedAt])
  @@map("host_notification_email_tasks")
}

// ==================== 反代站点模型 ====================

model ProxySite {
  id           Int             @id @default(autoincrement())
  instanceId   Int             @map("instance_id")
  hostId       Int             @map("host_id") // 冗余存储，方便查询
  domain       String // 域名（不允许泛域名）
  targetPort   Int             @map("target_port") // 实例内部端口
  httpsEnabled Boolean         @default(true) @map("https_enabled") // 是否启用 HTTPS（自动申请 Let's Encrypt 证书）
  remark       String? // 站点备注
  status       ProxySiteStatus @default(pending)
  enabled      Boolean         @default(true)
  error        String? // 错误信息
  createdAt    DateTime        @default(now()) @map("created_at")
  updatedAt    DateTime        @updatedAt @map("updated_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  host     Host     @relation(fields: [hostId], references: [id], onDelete: Cascade)

  @@unique([hostId, domain]) // 同一宿主机下域名唯一
  @@index([instanceId])
  @@index([hostId])
  @@map("proxy_sites")
}

// ==================== 登录记录模型 ====================

model LoginRecord {
  id        Int      @id @default(autoincrement())
  userId    Int      @map("user_id")
  ip        String   // 登录 IP
  country   String?  // 国家
  region    String?  // 地区/省份
  city      String?  // 城市
  isp       String?  // ISP 服务商
  timezone  String?  // 时区
  userAgent String?  @map("user_agent") // 用户代理
  createdAt DateTime @default(now()) @map("created_at")

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

  @@index([userId, createdAt(sort: Desc)])
  @@index([userId, ip])
  @@map("login_records")
}

// ==================== 工单系统模型 ====================

// 工单状态枚举
enum TicketStatus {
  open        // 待处理
  in_progress // 处理中
  resolved    // 已解决
  closed      // 已关闭
}

// 工单优先级枚举
enum TicketPriority {
  low
  normal
  high
  urgent
}

// 工单主表
model Ticket {
  id          Int            @id @default(autoincrement())
  userId      Int            @map("user_id")      // 发起工单的用户
  hostId      Int?           @map("host_id")      // 可选：关联的宿主机（无实例时为null，工单直接发给管理员）
  instanceId  Int?           @map("instance_id")  // 可选：关联的实例
  
  subject     String                              // 工单标题
  category    String         @default("general")  // 分类：general/billing/technical/abuse
  priority    TicketPriority @default(normal)
  status      TicketStatus   @default(open)
  
  createdAt   DateTime       @default(now()) @map("created_at")
  updatedAt   DateTime       @updatedAt @map("updated_at")
  resolvedAt  DateTime?      @map("resolved_at")
  closedAt    DateTime?      @map("closed_at")
  
  user        User           @relation("TicketCreator", fields: [userId], references: [id], onDelete: Cascade)
  host        Host?          @relation("HostTickets", fields: [hostId], references: [id], onDelete: Cascade)
  instance    Instance?      @relation("InstanceTickets", fields: [instanceId], references: [id], onDelete: SetNull)
  messages    TicketMessage[]
  attachments TicketMessageAttachment[]
  
  @@index([userId, status])
  @@index([hostId, status])
  @@index([status, createdAt(sort: Desc)])
  @@map("tickets")
}

// 工单消息表
model TicketMessage {
  id          Int      @id @default(autoincrement())
  ticketId    Int      @map("ticket_id")
  senderId    Int      @map("sender_id")    // 发送者（用户或宿主机所有者）
  content     String                         // 消息内容
  isFromOwner Boolean  @default(false) @map("is_from_owner") // 是否来自宿主机所有者
  createdAt   DateTime @default(now()) @map("created_at")
  
  ticket      Ticket   @relation(fields: [ticketId], references: [id], onDelete: Cascade)
  sender      User     @relation("TicketMessageSender", fields: [senderId], references: [id], onDelete: Cascade)
  attachments TicketMessageAttachment[]
  
  @@index([ticketId, createdAt])
  @@map("ticket_messages")
}

// 工单消息图片附件
model TicketMessageAttachment {
  id              Int      @id @default(autoincrement())
  ticketId        Int      @map("ticket_id")
  messageId       Int      @map("message_id")
  uploaderId      Int      @map("uploader_id")
  provider        String   @default("lsky")
  providerVersion String   @default("v1") @map("provider_version")
  providerFileId  String?  @map("provider_file_id")
  filename        String
  originalName    String   @map("original_name")
  mimeType        String   @map("mime_type")
  sizeBytes       Int      @map("size_bytes")
  width           Int?
  height          Int?
  url             String
  thumbnailUrl    String?  @map("thumbnail_url")
  createdAt       DateTime @default(now()) @map("created_at")

  ticket   Ticket        @relation(fields: [ticketId], references: [id], onDelete: Cascade)
  message  TicketMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
  uploader User          @relation("TicketAttachmentUploader", fields: [uploaderId], references: [id], onDelete: Cascade)

  @@index([ticketId, createdAt])
  @@index([messageId])
  @@index([uploaderId])
  @@map("ticket_message_attachments")
}

// ==================== 签到系统模型 ====================

// 兑换码类型枚举
enum RedeemCodeType {
  c // CPU
  r // 内存 (RAM)
  d // 硬盘 (Disk)
  t // 流量 (Traffic)
  p // 积分 (Points)
}

// 签到记录表
model CheckinRecord {
  id         Int            @id @default(autoincrement())
  userId     Int            @map("user_id")          // 签到用户（兑换码所有者）
  redeemCode String         @unique @map("redeem_code") // 兑换码
  codeType   RedeemCodeType @map("code_type")        // 兑换码类型
  codeValue  Int            @map("code_value")       // 兑换码数值
  expiresAt  DateTime       @map("expires_at")       // 过期时间（3小时）
  usedAt     DateTime?      @map("used_at")          // 使用时间
  usedBy     Int?           @map("used_by")          // 使用者ID
  usedFor    Int?           @map("used_for")         // 使用的实例ID
  createdAt  DateTime       @default(now()) @map("created_at")

  user       User      @relation("CheckinRecords", fields: [userId], references: [id], onDelete: Cascade)
  usedByUser User?     @relation("RedeemRecords", fields: [usedBy], references: [id], onDelete: SetNull)
  instance   Instance? @relation(fields: [usedFor], references: [id], onDelete: SetNull)

  @@index([userId])
  @@index([usedBy])
  @@index([expiresAt])
  @@map("checkin_records")
}

// 用户签到统计表
model CheckinStats {
  id                   Int       @id @default(autoincrement())
  userId               Int       @unique @map("user_id")
  lastCheckinDate      DateTime? @map("last_checkin_date")       // 最后签到日期
  lastRedeemDate       DateTime? @map("last_redeem_date")        // 最后兑换日期（签到码专用）
  consecutiveOthersUse Int       @default(0) @map("consecutive_others_use") // 连续被他人使用天数
  selfOnlyMode         Boolean   @default(false) @map("self_only_mode")     // 是否只能自己使用

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

  @@map("checkin_stats")
}

// ==================== 系统兑换码模型 ====================

// 系统兑换码（宿主机所有者生成）
model RedeemCode {
  id          Int            @id @default(autoincrement())
  code        String         @unique                        // 兑换码，格式: h-{random18}
  hostId      Int            @map("host_id")                // 关联的宿主机
  createdById Int            @map("created_by_id")          // 创建者（宿主机所有者）
  codeType    RedeemCodeType @map("code_type")              // 资源类型
  codeValue   Int            @map("code_value")             // 资源数值
  maxUses     Int            @default(1) @map("max_uses")   // 最大使用次数
  usedCount   Int            @default(0) @map("used_count") // 已使用次数
  expiresAt   DateTime?      @map("expires_at")             // 过期时间，null=永不过期
  enabled     Boolean        @default(true)                 // 是否启用
  remark      String?                                       // 备注
  batchId     String?        @map("batch_id")               // 批次ID，同批次码每用户只能用一张
  createdAt   DateTime       @default(now()) @map("created_at")

  host        Host             @relation(fields: [hostId], references: [id], onDelete: Cascade)
  createdBy   User             @relation("RedeemCodesCreated", fields: [createdById], references: [id], onDelete: Cascade)
  usages      RedeemCodeUsage[]

  @@index([hostId])
  @@index([createdById])
  @@index([enabled])
  @@index([batchId])
  @@map("redeem_codes")
}

// 系统兑换码使用记录
model RedeemCodeUsage {
  id           Int      @id @default(autoincrement())
  redeemCodeId Int      @map("redeem_code_id")  // 兑换码 ID
  userId       Int      @map("user_id")         // 使用者
  instanceId   Int      @map("instance_id")     // 应用到的实例
  batchId      String?  @map("batch_id")        // 批次ID（冗余存储，用于唯一约束）
  usedAt       DateTime @default(now()) @map("used_at")

  redeemCode RedeemCode @relation(fields: [redeemCodeId], references: [id], onDelete: Cascade)
  user       User       @relation("RedeemCodeUsages", fields: [userId], references: [id], onDelete: Cascade)
  instance   Instance   @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@unique([userId, batchId])  // 同批次每用户只能使用一次（NULL值不参与约束）
  @@index([redeemCodeId])
  @@index([userId])
  @@index([instanceId])
  @@map("redeem_code_usages")
}

// ==================== 用户自定义初始化命令模型 ====================

model CustomInitCommand {
  id          Int      @id @default(autoincrement())
  userId      Int      @map("user_id")
  name        String   // 模板名称
  command     String   @db.Text // 命令内容（多行文本）
  distros     String   // JSON数组，适配的发行版 ["ubuntu","alpine","all"]
  description String?  // 备注说明
  enabled     Boolean  @default(true) // 是否启用
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

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

  @@index([userId])
  @@index([userId, enabled])
  @@map("custom_init_commands")
}

model TerminalSavedCommand {
  id          Int      @id @default(autoincrement())
  userId      Int      @map("user_id")
  name        String
  command     String   @db.Text
  description String?
  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

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

  @@index([userId, updatedAt])
  @@map("terminal_saved_commands")
}

// ==================== 计费系统模型 ====================

// 余额变动日志
model BalanceLog {
  id            Int            @id @default(autoincrement())
  userId        Int            @map("user_id")
  type          BalanceLogType
  amount        Decimal        @db.Decimal(10, 2) // 正数=增加，负数=减少
  balanceBefore Decimal        @map("balance_before") @db.Decimal(10, 2)
  balanceAfter  Decimal        @map("balance_after") @db.Decimal(10, 2)

  // 关联信息
  orderId    String? @map("order_id")    // 充值订单号
  instanceId Int?    @map("instance_id") // 关联实例
  remark     String?                      // 备注

  createdAt DateTime @default(now()) @map("created_at")

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

  @@index([userId, createdAt(sort: Desc)])
  @@index([type])
  @@map("balance_logs")
}

// 支付渠道配置
model PaymentProvider {
  id     Int                   @id @default(autoincrement())
  name   String                // 显示名称，如"支付宝"、"微信支付"
  type   PaymentProviderType
  status PaymentProviderStatus @default(disabled)

  // 通用配置（JSON，根据类型存储不同字段）
  // 易支付: { pid, key, apiUrl, notifyUrl }
  // Stripe: { publicKey, secretKey, webhookSecret }
  config Json @default("{}")

  // 支付方式支持（JSON数组）
  // 如 ["alipay", "wxpay", "qqpay"] 或 ["card"]
  methods Json @default("[]")

  // 手续费设置
  feeRate  Decimal @default(0) @map("fee_rate") @db.Decimal(5, 4) // 费率，如 0.006 = 0.6%
  feeFixed Decimal @default(0) @map("fee_fixed") @db.Decimal(10, 2) // 固定手续费

  // 限额设置
  minAmount Decimal  @default(1) @map("min_amount") @db.Decimal(10, 2) // 最小充值金额
  maxAmount Decimal? @map("max_amount") @db.Decimal(10, 2)              // 最大充值金额

  sortOrder Int      @default(0) @map("sort_order") // 排序
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  rechargeRecords RechargeRecord[]

  @@map("payment_providers")
}

// 充值记录
model RechargeRecord {
  id         Int @id @default(autoincrement())
  userId     Int @map("user_id")
  providerId Int @map("provider_id")

  // 订单信息
  orderNo String  @unique @map("order_no") // 系统订单号
  tradeNo String? @map("trade_no")          // 第三方交易号

  // 金额
  amount       Decimal  @db.Decimal(10, 2)                                // 充值金额
  actualAmount Decimal? @map("actual_amount") @db.Decimal(10, 2)          // 实际到账金额
  fee          Decimal  @default(0) @db.Decimal(10, 2)                    // 平台手续费

  // 支付方式
  paymentMethod String? @map("payment_method") // alipay/wxpay/card 等

  status RechargeStatus @default(pending)

  // 回调信息
  callbackData Json?     @map("callback_data") // 支付回调原始数据
  callbackAt   DateTime? @map("callback_at")
  providerConfigSnapshot String? @map("provider_config_snapshot") // 订单创建时的支付渠道配置快照（加密）
  paymentDetails        Json?    @map("payment_details")          // 结构化支付详情（币种/网络/txid等）

  // 失败信息
  failReason String? @map("fail_reason")

  // IP 与设备
  ip        String?
  userAgent String? @map("user_agent")

  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")
  expiredAt   DateTime? @map("expired_at")   // 订单过期时间（按支付渠道发票有效期）
  completedAt DateTime? @map("completed_at")

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

  @@index([userId, status])
  @@index([status, createdAt])
  @@index([orderNo])
  @@index([tradeNo])
  @@map("recharge_records")
}

// VIP 等级规则配置
model VipLevelRule {
  id                  Int                   @id @default(autoincrement())
  type                VipLevelRuleType
  level               Int
  enabled             Boolean               @default(true)
  conditionMode       VipLevelConditionMode @default(any) @map("condition_mode")
  minRecharge         Decimal?              @map("min_recharge") @db.Decimal(10, 2)
  minConsume          Decimal?              @map("min_consume") @db.Decimal(10, 2)
  minHostingIncome    Decimal?              @map("min_hosting_income") @db.Decimal(10, 2)
  minHostingInstances Int?                  @map("min_hosting_instances")
  benefits            Json                  @default("{}")
  createdAt           DateTime              @default(now()) @map("created_at")
  updatedAt           DateTime              @updatedAt @map("updated_at")

  @@unique([type, level])
  @@index([type, enabled])
  @@map("vip_level_rules")
}

model VipBenefitReward {
  id          Int                  @id @default(autoincrement())
  level       Int
  type        VipBenefitRewardType
  title       String
  description String?
  claimLimit  Int                  @default(1) @map("claim_limit")
  sortOrder   Int                  @default(0) @map("sort_order")
  enabled     Boolean              @default(true)
  config      Json                 @default("{}")
  createdAt   DateTime             @default(now()) @map("created_at")
  updatedAt   DateTime             @updatedAt @map("updated_at")

  claims VipBenefitClaim[]

  @@index([level, enabled, sortOrder])
  @@index([type])
  @@map("vip_benefit_rewards")
}

model VipBenefitClaim {
  id          Int                   @id @default(autoincrement())
  userId      Int                   @map("user_id")
  rewardId    Int                   @map("reward_id")
  level       Int
  status      VipBenefitClaimStatus
  claimNo     Int                   @map("claim_no")
  snapshot    Json                  @default("{}")
  deliveredAt DateTime?             @map("delivered_at")
  createdAt   DateTime              @default(now()) @map("created_at")

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

  @@unique([userId, rewardId, claimNo])
  @@index([userId, level])
  @@index([rewardId])
  @@index([status, createdAt])
  @@map("vip_benefit_claims")
}

// 支付回调防重放记录
model PaymentCallback {
  id         Int      @id @default(autoincrement())
  providerId Int      @map("provider_id")     // 支付渠道ID
  orderNo    String   @map("order_no")        // 订单号
  tradeNo    String?  @map("trade_no")        // 第三方交易号
  callbackIp String?  @map("callback_ip")     // 回调IP
  processed  Boolean  @default(true)          // 是否已处理
  createdAt  DateTime @default(now()) @map("created_at")

  @@unique([providerId, orderNo, tradeNo])    // 联合唯一索引防重放
  @@index([createdAt])                        // 用于定期清理
  @@map("payment_callbacks")
}

// 套餐方案（付费实例的核心）
model PackagePlan {
  id        Int @id @default(autoincrement())
  packageId Int @map("package_id")

  // 基本信息
  name        String  // 方案名称，如"低配"、"高配"、"标准版"
  description String? // 方案描述

  // ========== 资源配置（预设固定值） ==========
  cpu    Int // CPU 额度 (%)，如 50, 100, 200
  memory Int // 内存 (MB)，如 512, 1024, 2048
  disk   Int // 磁盘 (MB)，如 10240, 20480, 51200

  // ========== 配额限制（方案级别独立配置） ==========
  portLimit         Int    @map("port_limit")          // 端口映射数上限
  snapshotLimit     Int    @map("snapshot_limit")      // 快照数上限
  backupLimit       Int    @map("backup_limit")        // 备份数上限
  siteLimit         Int    @map("site_limit")          // 反代站点数上限
  swapSize          Int    @default(0) @map("swap_size") // SWAP 大小(MB)，0 表示不提供
  trafficLimit      BigInt @map("traffic_limit")       // 月流量限额 (Bytes)
  trafficLimitSpeed String @default("1Mbit") @map("traffic_limit_speed") // 流量超限后的限速值

  // ========== 计费配置 ==========
  price        Decimal @db.Decimal(10, 2)                             // 价格
  billingCycle Int     @default(1) @map("billing_cycle")              // 计费周期（月）
  setupFee     Decimal @default(0) @map("setup_fee") @db.Decimal(10, 2) // 开通费

  // ========== 状态与排序 ==========
  isActive  Boolean @default(true) @map("is_active") // 是否启用
  isSoldOut Boolean @default(false) @map("is_sold_out") // 是否售罄：显示但不可新购/变更
  sortOrder Int     @default(0) @map("sort_order")   // 排序

  // ========== SLA保证（可选） ==========
  slaGuarantee Decimal? @map("sla_guarantee") @db.Decimal(5, 2) // SLA保证百分比，1-100，两位小数，null=不显示

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

  // 关系
  package   Package    @relation(fields: [packageId], references: [id], onDelete: Cascade)
  instances Instance[]
  affCodes  AffCode[]  // AFF优惠码

  @@unique([packageId, name]) // 同一套餐下方案名称唯一
  @@index([packageId, isActive])
  @@map("package_plans")
}

// 实例计费记录
model InstanceBillingRecord {
  id         Int @id @default(autoincrement())
  instanceId Int @map("instance_id")
  userId     Int @map("user_id")

  type   BillingRecordType
  amount Decimal           @db.Decimal(10, 2) // 扣费金额（正数）
  months Int               @default(1)        // 购买/续费月数

  // 计费周期
  periodStart DateTime @map("period_start") // 计费开始时间
  periodEnd   DateTime @map("period_end")   // 计费结束时间

  // 关联
  balanceLogId Int? @map("balance_log_id") // 关联余额日志

  remark    String?
  createdAt DateTime @default(now()) @map("created_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@index([instanceId, createdAt(sort: Desc)])
  @@index([userId, createdAt(sort: Desc)])
  @@map("instance_billing_records")
}

// ==================== AFF推荐计划模型 ====================

// AFF优惠码
model AffCode {
  id              Int      @id @default(autoincrement())
  code            String   @unique                                // 优惠码，如 AFF-123-A8K3M9
  userId          Int      @map("user_id")                        // 创建者
  packagePlanId   Int?     @map("package_plan_id")                // 绑定的方案（null表示全局码）
  discountRate    Decimal  @default(0.05) @map("discount_rate") @db.Decimal(5, 4)   // 折扣率 5%
  commissionRate  Decimal  @default(0.05) @map("commission_rate") @db.Decimal(5, 4) // 返利率 5%
  enabled         Boolean  @default(true)
  usedCount       Int      @default(0) @map("used_count")         // 使用次数
  totalEarnings   Decimal  @default(0) @map("total_earnings") @db.Decimal(10, 2) // 总收益
  createdAt       DateTime @default(now()) @map("created_at")

  user        User         @relation(fields: [userId], references: [id], onDelete: Cascade)
  packagePlan PackagePlan? @relation(fields: [packagePlanId], references: [id], onDelete: Cascade)
  bindings    AffBinding[]
  mailSubscriptionBindings MailSubscriptionAffBinding[]
  logs        AffLog[]

  @@unique([userId, packagePlanId]) // 每用户每方案仅一个优惠码（包括全局码：packagePlanId=null时每用户最多一个）
  @@index([userId])
  @@index([packagePlanId])
  @@map("aff_codes")
}

// 实例与优惠码永久绑定
model AffBinding {
  id          Int      @id @default(autoincrement())
  instanceId  Int      @unique @map("instance_id")  // 一个实例只能绑定一个优惠码
  affCodeId   Int      @map("aff_code_id")
  createdAt   DateTime @default(now()) @map("created_at")

  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
  affCode  AffCode  @relation(fields: [affCodeId], references: [id], onDelete: Cascade)

  @@index([affCodeId])
  @@map("aff_bindings")
}

// 邮箱订阅与优惠码永久绑定
model MailSubscriptionAffBinding {
  id                 Int      @id @default(autoincrement())
  mailSubscriptionId Int      @unique @map("mail_subscription_id")
  affCodeId          Int      @map("aff_code_id")
  createdAt          DateTime @default(now()) @map("created_at")

  mailSubscription MailSubscription @relation(fields: [mailSubscriptionId], references: [id], onDelete: Cascade)
  affCode          AffCode          @relation(fields: [affCodeId], references: [id], onDelete: Cascade)

  @@index([affCodeId])
  @@map("mail_subscription_aff_bindings")
}

// AFF余额变动日志
model AffLog {
  id                   Int        @id @default(autoincrement())
  userId               Int        @map("user_id")                // 收益用户
  affCodeId            Int?       @map("aff_code_id")            // 关联优惠码
  instanceId           Int?       @map("instance_id")            // 关联实例
  mailSubscriptionId   Int?       @map("mail_subscription_id")   // 关联邮箱订阅
  type                 AffLogType
  amount               Decimal    @db.Decimal(10, 2)             // 正数=收入，负数=转出
  originalAmount       Decimal?   @map("original_amount") @db.Decimal(10, 2) // 原始订单金额
  balanceBefore        Decimal    @map("balance_before") @db.Decimal(10, 2)
  balanceAfter         Decimal    @map("balance_after") @db.Decimal(10, 2)
  remark               String?
  createdAt            DateTime   @default(now()) @map("created_at")

  user             User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  affCode          AffCode?          @relation(fields: [affCodeId], references: [id], onDelete: SetNull)
  mailSubscription MailSubscription? @relation(fields: [mailSubscriptionId], references: [id], onDelete: SetNull)

  @@index([userId, createdAt(sort: Desc)])
  @@index([affCodeId])
  @@index([mailSubscriptionId])
  @@map("aff_logs")
}

// AFF余额转化申请
model AffWithdrawal {
  id            Int                 @id @default(autoincrement())
  userId        Int                 @map("user_id")
  amount        Decimal             @db.Decimal(10, 2)  // 申请转化金额
  status        AffWithdrawalStatus @default(pending)
  rejectReason  String?             @map("reject_reason")
  reviewedBy    Int?                @map("reviewed_by")  // 审核管理员ID
  reviewedAt    DateTime?           @map("reviewed_at")
  createdAt     DateTime            @default(now()) @map("created_at")

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

  @@index([userId, status])
  @@index([status, createdAt])
  @@map("aff_withdrawals")
}

// ==================== 积分系统模型 ====================

// 积分日志类型
enum PointsLogType {
  convert       // 消费兑换
  lottery_win   // 抽奖获得
  lottery_spend // 抽奖消耗
  admin_adjust  // 管理员调整
  checkin       // 签到奖励
  badge_draw_spend   // 徽章随机抽取消耗
  badge_select_spend // 徽章自选消耗
  invite_generate    // 生成邀请码消耗
  vip_benefit        // VIP福利领取
}

enum UserBadgeSource {
  draw
  lottery
  select
  admin_grant
}

enum BadgeApplicationTarget {
  avatar
  instance
}

// 用户积分表
model UserPoints {
  userId            Int       @id @map("user_id")              // user_id 是主键
  points            Int       @default(0)                      // 当前积分
  totalEarned       Int       @default(0) @map("total_earned") // 累计获得
  totalSpent        Int       @default(0) @map("total_spent")  // 累计消耗
  lastConvertedAt   DateTime? @map("last_converted_at")        // 上次兑换时间
  convertedConsume  Decimal   @default(0) @db.Decimal(10, 2) @map("converted_consume") // 已兑换的消费金额
  createdAt         DateTime  @default(now()) @map("created_at")
  updatedAt         DateTime  @updatedAt @map("updated_at")

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

  @@map("user_points")
}

// 积分变动日志
model PointsLog {
  id           Int           @id @default(autoincrement())
  userId       Int           @map("user_id")
  type         PointsLogType
  amount       Int                                                   // 变动数量（正数为获得，负数为消耗）
  pointsBefore Int           @map("points_before")
  pointsAfter  Int           @map("points_after")
  relatedId    Int?          @map("related_id")                      // 关联ID（抽奖记录ID等）
  remark       String?
  createdAt    DateTime      @default(now()) @map("created_at")

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

  @@index([userId, createdAt(sort: Desc)])
  @@map("points_logs")
}

model BadgeSeries {
  id            String   @id
  title         String
  nameZh        String   @map("name_zh")
  nameEn        String?  @map("name_en")
  description   String
  sourceId      String?  @map("source_id")
  sourceLabel   String?  @map("source_label")
  displayOrder  Int      @default(0) @map("display_order")
  isActive      Boolean  @default(true) @map("is_active")
  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")

  badges Badge[]

  @@index([displayOrder])
  @@index([isActive])
  @@map("badge_series")
}

model Badge {
  id            String   @id
  name          String
  nameEn        String?  @map("name_en")
  fullLabel     String   @map("full_label")
  seriesId      String   @map("series_id")
  sourceId      String?  @map("source_id")
  sourceLabel   String?  @map("source_label")
  assetUrl      String   @map("asset_url")
  assetUrlDark  String?  @map("asset_url_dark")
  assetUrlLight String?  @map("asset_url_light")
  displayOrder  Int      @default(0) @map("display_order")
  isActive      Boolean  @default(true) @map("is_active")
  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")

  series             BadgeSeries          @relation(fields: [seriesId], references: [id], onDelete: Restrict)

  @@index([seriesId, displayOrder])
  @@index([isActive])
  @@map("badges")
}

model UserBadgeOwnership {
  id                Int                     @id @default(autoincrement())
  userId            Int                     @map("user_id")
  badgeId           String                  @map("badge_id")
  source            UserBadgeSource         @default(draw)
  applicationTarget BadgeApplicationTarget? @map("application_target")
  appliedInstanceId Int?                    @map("applied_instance_id")
  appliedAt         DateTime?               @map("applied_at")
  createdAt         DateTime                @default(now()) @map("created_at")
  updatedAt         DateTime                @updatedAt @map("updated_at")

  user            User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  appliedInstance Instance? @relation("InstanceAppliedBadgeOwnerships", fields: [appliedInstanceId], references: [id], onDelete: SetNull)

  @@index([userId, createdAt(sort: Desc)])
  @@index([userId, badgeId])
  @@index([userId, applicationTarget])
  @@index([appliedInstanceId])
  @@map("user_badge_ownerships")
}

// ==================== 抽奖系统模型 ====================

// 奖品类型
enum LotteryPrizeType {
  nothing   // 再接再历
  points    // 积分奖励
  balance   // 余额奖励
  badge     // 随机徽章奖励
  instance  // 实例奖励（需手动发放）
  cpu       // CPU资源（存入资源池）
  memory    // 内存资源（存入资源池）
  disk      // 硬盘资源（存入资源池）
  traffic   // 流量资源（存入资源池）
}

// 中奖记录状态
enum LotteryRecordStatus {
  pending   // 待发放（仅实例奖励）
  delivered // 已发放
  claimed   // 已领取（用户提交工单后）
}

// 抽奖活动
model Lottery {
  id              Int       @id @default(autoincrement())
  name            String                                             // 抽奖名称
  description     String?                                            // 描述
  costPoints      Int       @map("cost_points")                      // 每次抽奖消耗积分
  isActive        Boolean   @default(true) @map("is_active")         // 是否启用
  startAt         DateTime? @map("start_at")                         // 开始时间（可选）
  endAt           DateTime? @map("end_at")                           // 结束时间（可选）
  totalDraws      Int       @default(0) @map("total_draws")          // 总抽奖次数
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")
  createdBy       Int       @map("created_by")                       // 创建者管理员ID

  prizes             LotteryPrize[]
  records            LotteryRecord[]
  notificationConfig LotteryNotificationConfig?

  @@map("lotteries")
}

// 抽奖奖品
model LotteryPrize {
  id              Int              @id @default(autoincrement())
  lotteryId       Int              @map("lottery_id")
  name            String                                             // 奖品名称
  type            LotteryPrizeType                                   // 奖品类型
  value           Int              @default(0)                       // 奖品值（积分数/余额分/实例配置ID）
  probability     Decimal          @db.Decimal(5, 2)                 // 中奖概率（百分比，如 30.00 表示 30%）
  totalQuantity   Int?             @map("total_quantity")            // 总数量（null表示无限）
  remainQuantity  Int?             @map("remain_quantity")           // 剩余数量
  displayOrder    Int              @default(0) @map("display_order") // 显示顺序
  createdAt       DateTime         @default(now()) @map("created_at")

  // 实例奖励专用字段
  instanceDesc    String?          @map("instance_desc")             // 实例描述（如：1核1G 30天）

  lottery         Lottery          @relation(fields: [lotteryId], references: [id], onDelete: Cascade)
  records         LotteryRecord[]

  @@index([lotteryId])
  @@map("lottery_prizes")
}

// 中奖记录
model LotteryRecord {
  id               Int                 @id @default(autoincrement())
  lotteryId        Int                 @map("lottery_id")
  prizeId          Int                 @map("prize_id")
  userId           Int                 @map("user_id")
  prizeType        LotteryPrizeType    @map("prize_type")
  prizeValue       Int                 @map("prize_value")            // 中奖时的奖品值（快照）
  prizeName        String              @map("prize_name")             // 中奖时的奖品名称（快照）
  status           LotteryRecordStatus @default(delivered)
  pointsSpent      Int                 @map("points_spent")           // 消耗的积分
  deliveredAt      DateTime?           @map("delivered_at")           // 发放时间
  deliveredBy      Int?                @map("delivered_by")           // 发放管理员（实例奖励）
  ticketId         Int?                @map("ticket_id")              // 关联工单ID（实例奖励）
  notificationSent Boolean             @default(false) @map("notification_sent")
  createdAt        DateTime            @default(now()) @map("created_at")

  lottery Lottery      @relation(fields: [lotteryId], references: [id], onDelete: Cascade)
  prize   LotteryPrize @relation(fields: [prizeId], references: [id], onDelete: Cascade)
  user    User         @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId, createdAt(sort: Desc)])
  @@index([lotteryId])
  @@index([status])
  @@map("lottery_records")
}

// 抽奖通知配置（管理员为抽奖配置的通知渠道）
model LotteryNotificationConfig {
  id              Int      @id @default(autoincrement())
  lotteryId       Int      @unique @map("lottery_id")
  enabled         Boolean  @default(true)

  // 通知方式配置（与现有通知渠道类型一致）
  type            String                                             // telegram, discord, webhook
  config          Json                                               // { botToken, chatId } 或 { webhookUrl } 等

  // 通知条件
  notifyBalance   Boolean  @default(true) @map("notify_balance")     // 余额中奖通知
  notifyInstance  Boolean  @default(true) @map("notify_instance")    // 实例中奖通知
  // 注意：积分中奖不通知

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

  lottery         Lottery  @relation(fields: [lotteryId], references: [id], onDelete: Cascade)

  @@map("lottery_notification_configs")
}

// ==================== 用户资源池系统 ====================

// 资源池操作类型枚举
enum ResourcePoolAction {
  checkin       // 签到获得
  redeem        // 兑换码兑换
  admin_grant   // 管理员赠送
  system_grant  // 系统活动奖励
  lottery       // 抽奖获得
  apply         // 应用到实例
  system_redeem // 系统h-兑换码兑换
}

// 用户资源池
model UserResourcePool {
  id        Int      @id @default(autoincrement())
  userId    Int      @unique @map("user_id")
  cpu       Int      @default(0)  // CPU额度 (%)
  memory    Int      @default(0)  // 内存额度 (MB)
  disk      Int      @default(0)  // 硬盘额度 (MB)
  traffic   BigInt   @default(0)  // 流量额度 (GB)
  updatedAt DateTime @updatedAt @map("updated_at")

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

  @@map("user_resource_pools")
}

// 资源池变动记录
model ResourcePoolLog {
  id           Int                @id @default(autoincrement())
  userId       Int                @map("user_id")
  action       ResourcePoolAction
  resourceType RedeemCodeType     @map("resource_type") // c/r/d/t
  amount       Int                // 正数=获得，负数=消耗
  instanceId   Int?               @map("instance_id")   // 应用到的实例
  remark       String?
  createdAt    DateTime           @default(now()) @map("created_at")

  user     User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  instance Instance? @relation(fields: [instanceId], references: [id], onDelete: SetNull)

  @@index([userId])
  @@index([userId, action])
  @@map("resource_pool_logs")
}

// 用户实例销毁记录
model UserDestroyRecord {
  id           Int      @id @default(autoincrement())
  userId       Int      @map("user_id")
  hostId       Int      @map("host_id")       // 节点ID（用于90天节点冷却期）
  instanceId   Int      @map("instance_id")   // 被销毁的实例ID
  instanceName String   @map("instance_name") // 实例名称（备份，因实例已删除）
  destroyedAt  DateTime @default(now()) @map("destroyed_at")
  refundAmount Decimal  @db.Decimal(10, 2) @map("refund_amount") // 实际退款金额
  feeAmount    Decimal  @db.Decimal(10, 2) @map("fee_amount")    // 手续费金额
  isFirstTime  Boolean  @map("is_first_time") // 是否首次销毁（首次免手续费）

  user     User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  host     Host     @relation(fields: [hostId], references: [id], onDelete: Cascade)
  instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)

  @@unique([instanceId])
  @@index([userId])
  @@index([userId, hostId])
  @@map("user_destroy_records")
}

// ==================== 托管余额系统模型 ====================

enum HostingBalanceType {
  income    // 收入（用户购买/续费）
  unfreeze  // 解冻
  withdraw  // 提现
  deduction // 扣除（用户销毁托管实例）
}

// 托管收支操作类型（用于区分具体操作）
enum HostingActionType {
  purchase     // 用户开通实例
  renew        // 用户续费实例
  upgrade      // 用户升级方案
  destroy      // 用户销毁实例
  unfreeze     // 自动解冻
  withdraw     // 提现
  admin_adjust // 管理员调整
}

enum WithdrawalStatus {
  pending   // 待审核
  approved  // 已通过
  rejected  // 已拒绝
  completed // 已完成
}

enum WithdrawalTarget {
  balance // 提现到面板余额
  usdt    // 提现到USDT
}

// ==================== 域名邮箱模块 ====================

// 域名验证状态
enum MailDomainStatus {
  pending   // 等待 DNS 验证
  verified  // 已验证
  suspended // 已暂停
}

// 邮箱订阅状态
enum MailSubscriptionStatus {
  active    // 正常
  expired   // 已过期
  suspended // 已暂停
}

// 计费周期
enum MailBillingCycle {
  monthly // 月付
  yearly  // 年付
}

// 邮箱源（数据中心）
model MailSource {
  id            Int      @id @default(autoincrement())
  name          String   // 名称，如"美国拉斯维加斯"
  code          String   @unique // 地区代码，如"us"
  apiUrl        String   @map("api_url")      // CraneMail Reseller API 地址
  apiKey        String   @map("api_key")      // CraneMail Reseller API Key
  smarterMailUrl String  @map("smartermail_url") // SmarterMail API 地址
  enabled       Boolean  @default(true)
  sortOrder     Int      @default(0) @map("sort_order")
  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")

  plans         MailPlan[]
  subscriptions MailSubscription[]
  domains       MailDomain[]

  @@map("mail_sources")
}

// 套餐方案
model MailPlan {
  id            Int              @id @default(autoincrement())
  sourceId      Int              @map("source_id")
  name          String           // 方案名称，如"基础版"
  description   String?          // 方案描述
  domainLimit   Int              @map("domain_limit")  // 主域名数量限制
  diskLimitGb   Int              @map("disk_limit_gb") // 存储空间 GB
  billingCycle  MailBillingCycle @map("billing_cycle") // 计费周期
  price         Decimal          @db.Decimal(10, 2)    // 价格
  enabled       Boolean          @default(true)
  sortOrder     Int              @default(0) @map("sort_order")
  createdAt     DateTime         @default(now()) @map("created_at")
  updatedAt     DateTime         @updatedAt @map("updated_at")

  source        MailSource       @relation(fields: [sourceId], references: [id], onDelete: Cascade)
  subscriptions MailSubscription[]

  @@index([sourceId, enabled])
  @@map("mail_plans")
}

// 用户订阅
model MailSubscription {
  id           Int                    @id @default(autoincrement())
  userId       Int                    @map("user_id")
  sourceId     Int                    @map("source_id")
  planId       Int                    @map("plan_id")
  status       MailSubscriptionStatus @default(active)
  domainLimit  Int                    @map("domain_limit")  // 冗余存储购买时的限制
  diskLimitGb  Int                    @map("disk_limit_gb") // 冗余存储购买时的限制
  expiresAt    DateTime               @map("expires_at")
  autoRenew    Boolean                @default(false) @map("auto_renew")
  createdAt    DateTime               @default(now()) @map("created_at")
  updatedAt    DateTime               @updatedAt @map("updated_at")

  user         User                   @relation(fields: [userId], references: [id], onDelete: Cascade)
  source       MailSource             @relation(fields: [sourceId], references: [id], onDelete: Cascade)
  plan         MailPlan               @relation(fields: [planId], references: [id], onDelete: Cascade)
  domains      MailDomain[]
  affBinding   MailSubscriptionAffBinding?
  affLogs      AffLog[]

  @@index([userId, status])
  @@index([expiresAt])
  @@map("mail_subscriptions")
}

// 域名
model MailDomain {
  id             Int              @id @default(autoincrement())
  subscriptionId Int              @map("subscription_id")
  sourceId       Int              @map("source_id")
  domain         String           // 域名
  status         MailDomainStatus @default(pending)
  adminUsername  String?          @map("admin_username") // CraneMail 返回的管理员账号
  adminPassword  String?          @map("admin_password") // CraneMail 返回的管理员密码（加密存储）
  diskUsedMb     Int              @default(0) @map("disk_used_mb") // 已用空间 MB
  verifiedAt     DateTime?        @map("verified_at")
  createdAt      DateTime         @default(now()) @map("created_at")
  updatedAt      DateTime         @updatedAt @map("updated_at")

  subscription   MailSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
  source         MailSource       @relation(fields: [sourceId], references: [id], onDelete: Cascade)
  accounts       MailAccount[]

  @@unique([domain, sourceId])
  @@index([subscriptionId])
  @@map("mail_domains")
}

// 邮箱账户
model MailAccount {
  id          Int         @id @default(autoincrement())
  domainId    Int         @map("domain_id")
  email       String      // 完整邮箱地址
  username    String      // 用户名部分 (@ 前面)
  displayName String?     @map("display_name") // 显示名称
  diskLimitMb Int         @default(2048) @map("disk_limit_mb") // 邮箱容量 MB
  diskUsedMb  Int         @default(0) @map("disk_used_mb")     // 已用空间 MB
  isAdmin     Boolean     @default(false) @map("is_admin")     // 是否为域管理员
  createdAt   DateTime    @default(now()) @map("created_at")
  updatedAt   DateTime    @updatedAt @map("updated_at")

  domain      MailDomain  @relation(fields: [domainId], references: [id], onDelete: Cascade)

  @@unique([domainId, username])
  @@index([domainId])
  @@map("mail_accounts")
}

// 托管余额变动日志
model HostingBalanceLog {
  id         Int                 @id @default(autoincrement())
  userId     Int                 @map("user_id")
  type       HostingBalanceType
  actionType HostingActionType?  @map("action_type") // 具体操作类型（开通/续费/升级/销毁等）
  amount     Decimal             @db.Decimal(10, 2) // 金额（正数）
  frozen     Boolean             @default(true)     // 是否冻结状态
  unfreezeAt DateTime?           @map("unfreeze_at") // 解冻时间
  relatedId  Int?                @map("related_id") // 关联ID（实例ID/提现ID）
  remark     String?

  // Snapshot fields for data persistence
  snapshotBuyerName    String?   @map("snapshot_buyer_name")
  snapshotBuyerEmail   String?   @map("snapshot_buyer_email")
  snapshotBuyerAvatar  String?   @map("snapshot_buyer_avatar")
  snapshotInstanceName String?   @map("snapshot_instance_name")
  snapshotHostName     String?   @map("snapshot_host_name")
  snapshotPackageName  String?   @map("snapshot_package_name")
  snapshotPlanName     String?   @map("snapshot_plan_name")

  createdAt  DateTime            @default(now()) @map("created_at")

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

  @@index([userId, frozen])
  @@index([unfreezeAt])
  @@map("hosting_balance_logs")
}

// 托管余额提现记录
model HostingWithdrawal {
  id           Int              @id @default(autoincrement())
  userId       Int              @map("user_id")
  amount       Decimal          @db.Decimal(10, 2) // 提现金额
  feeRate      Decimal          @map("fee_rate") @db.Decimal(5, 4)  // 手续费率（如 0.1000 表示 10%）
  feeAmount    Decimal          @map("fee_amount") @db.Decimal(10, 2) // 手续费金额
  actualAmount Decimal          @map("actual_amount") @db.Decimal(10, 2) // 实际到账金额
  target       WithdrawalTarget
  usdtAddress  String?          @map("usdt_address") // USDT地址（仅USDT提现）
  status       WithdrawalStatus @default(pending)
  rejectReason String?          @map("reject_reason")
  processedAt  DateTime?        @map("processed_at")
  processedBy  Int?             @map("processed_by") // 处理的管理员ID
  createdAt    DateTime         @default(now()) @map("created_at")

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

  @@index([userId, status])
  @@map("hosting_withdrawals")
}

// 托管用户拉黑关系
model HostingUserBlock {
  id            Int      @id @default(autoincrement())
  blockerId     Int      @map("blocker_id") // 托管用户
  blockedUserId Int      @map("blocked_user_id") // 被拉黑用户
  remark        String?
  createdAt     DateTime @default(now()) @map("created_at")

  blocker     User @relation("HostingBlocker", fields: [blockerId], references: [id], onDelete: Cascade)
  blockedUser User @relation("HostingBlockedUser", fields: [blockedUserId], references: [id], onDelete: Cascade)

  @@unique([blockerId, blockedUserId])
  @@index([blockedUserId])
  @@index([blockerId])
  @@map("hosting_user_blocks")
}

// 托管专区机主
model HostingZone {
  id        Int      @id @default(autoincrement())
  name      String
  ownerId   Int      @unique @map("owner_id")
  logoUrl   String   @map("logo_url") @db.Text
  sortOrder Int      @default(0) @map("sort_order")
  active    Boolean  @default(true)
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  owner User @relation("HostingZoneOwner", fields: [ownerId], references: [id], onDelete: Cascade)

  @@index([active, sortOrder])
  @@map("hosting_zones")
}
