// 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"
}

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

// ============================================================================
// 2.0 用户表
// ============================================================================

model User {
  id              BigInt     @id @default(autoincrement())

  // 基本信息
  phone           String     @unique
  passwordHash    String     @map("password_hash")
  tradePasswordHash String?  @map("trade_password_hash") // 支付密码（独立于登录密码）

  // 统一关联键 (跨所有服务)
  // V1: 12位 (D + 6位日期 + 5位序号), 如 D2512110008
  // V2: 15位 (D + 6位日期 + 8位序号), 如 D25121100000008
  accountSequence String     @unique @map("account_sequence")

  // 状态
  status          UserStatus @default(ACTIVE)

  // KYC 实名认证
  kycStatus       KycStatus  @default(PENDING)
  realName        String?    @map("real_name")
  idCardNo        String?    @map("id_card_no")
  idCardFront     String?    @map("id_card_front")   // 身份证正面照片路径
  idCardBack      String?    @map("id_card_back")    // 身份证背面照片路径
  kycSubmittedAt  DateTime?  @map("kyc_submitted_at")
  kycVerifiedAt   DateTime?  @map("kyc_verified_at")
  kycRejectReason String?    @map("kyc_reject_reason")

  // 安全
  loginFailCount  Int        @default(0) @map("login_fail_count")
  lockedUntil     DateTime?  @map("locked_until")
  lastLoginAt     DateTime?  @map("last_login_at")
  lastLoginIp     String?    @map("last_login_ip")

  // 时间戳
  createdAt       DateTime   @default(now()) @map("created_at")
  updatedAt       DateTime   @updatedAt @map("updated_at")

  // 关联
  refreshTokens   RefreshToken[]
  loginLogs       LoginLog[]
  smsLogs         SmsLog[]

  @@index([phone])
  @@index([accountSequence])
  @@index([status])
  @@index([kycStatus])
  @@map("users")
}

enum UserStatus {
  ACTIVE
  DISABLED
  DELETED
}

enum KycStatus {
  PENDING     // 待提交
  SUBMITTED   // 已提交待审核
  VERIFIED    // 已认证
  REJECTED    // 已拒绝
}

// ============================================================================
// CDC 同步的 1.0 用户（只读）
// ============================================================================

model SyncedLegacyUser {
  id                BigInt    @id @default(autoincrement())

  // 1.0 用户数据
  legacyId          BigInt    @unique @map("legacy_id")      // 1.0 的 user.id
  accountSequence   String    @unique @map("account_sequence")
  phone             String?   // 系统账户可能没有手机号
  passwordHash      String?   @map("password_hash")  // 系统账户可能没有密码
  nickname          String?   // 昵称 (from identity-service)
  status            String
  legacyCreatedAt   DateTime  @map("legacy_created_at")

  // 迁移状态
  migratedToV2      Boolean   @default(false) @map("migrated_to_v2")
  migratedAt        DateTime? @map("migrated_at")

  // CDC 元数据
  sourceSequenceNum BigInt    @map("source_sequence_num")
  syncedAt          DateTime  @default(now()) @map("synced_at")

  @@index([phone])
  @@index([accountSequence])
  @@index([migratedToV2])
  @@map("synced_legacy_users")
}

// ============================================================================
// CDC 同步的 1.0 钱包地址（只读）
// ============================================================================

model SyncedWalletAddress {
  id                BigInt    @id @default(autoincrement())

  // 1.0 钱包地址数据
  legacyAddressId   BigInt    @unique @map("legacy_address_id")  // 1.0 的 wallet_addresses.address_id
  legacyUserId      BigInt    @map("legacy_user_id")             // 1.0 的 wallet_addresses.user_id
  chainType         String    @map("chain_type")                 // KAVA, BSC 等
  address           String                                       // 钱包地址
  publicKey         String    @map("public_key")                 // MPC 公钥
  status            String    @default("ACTIVE")                 // ACTIVE, DELETED

  legacyBoundAt     DateTime  @map("legacy_bound_at")            // 1.0 绑定时间

  // CDC 元数据
  sourceSequenceNum BigInt    @map("source_sequence_num")
  syncedAt          DateTime  @default(now()) @map("synced_at")

  @@unique([legacyUserId, chainType])
  @@index([legacyUserId])
  @@index([chainType, address])
  @@map("synced_wallet_addresses")
}

// ============================================================================
// 刷新令牌
// ============================================================================

model RefreshToken {
  id          BigInt    @id @default(autoincrement())
  userId      BigInt    @map("user_id")
  token       String    @unique
  deviceInfo  String?   @map("device_info")
  ipAddress   String?   @map("ip_address")
  expiresAt   DateTime  @map("expires_at")
  createdAt   DateTime  @default(now()) @map("created_at")
  revokedAt   DateTime? @map("revoked_at")

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

  @@index([userId])
  @@index([token])
  @@index([expiresAt])
  @@map("refresh_tokens")
}

// ============================================================================
// 短信验证码
// ============================================================================

model SmsVerification {
  id          BigInt            @id @default(autoincrement())
  phone       String
  code        String
  type        SmsVerificationType
  expiresAt   DateTime          @map("expires_at")
  verifiedAt  DateTime?         @map("verified_at")
  attempts    Int               @default(0)
  createdAt   DateTime          @default(now()) @map("created_at")

  @@index([phone, type])
  @@index([expiresAt])
  @@map("sms_verifications")
}

enum SmsVerificationType {
  REGISTER          // 注册
  LOGIN             // 登录
  RESET_PASSWORD    // 重置密码
  CHANGE_PHONE      // 更换手机号
}

// ============================================================================
// 短信发送日志
// ============================================================================

model SmsLog {
  id          BigInt    @id @default(autoincrement())
  userId      BigInt?   @map("user_id")
  phone       String
  type        SmsVerificationType
  content     String?
  status      SmsStatus @default(PENDING)
  provider    String?               // 短信服务商
  providerId  String?   @map("provider_id")  // 服务商返回的ID
  errorMsg    String?   @map("error_msg")
  createdAt   DateTime  @default(now()) @map("created_at")

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

  @@index([phone])
  @@index([userId])
  @@index([createdAt])
  @@map("sms_logs")
}

enum SmsStatus {
  PENDING
  SENT
  DELIVERED
  FAILED
}

// ============================================================================
// 登录日志
// ============================================================================

model LoginLog {
  id          BigInt      @id @default(autoincrement())
  userId      BigInt?     @map("user_id")
  phone       String
  type        LoginType
  success     Boolean
  failReason  String?     @map("fail_reason")
  ipAddress   String?     @map("ip_address")
  userAgent   String?     @map("user_agent")
  deviceInfo  String?     @map("device_info")
  createdAt   DateTime    @default(now()) @map("created_at")

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

  @@index([userId])
  @@index([phone])
  @@index([createdAt])
  @@map("login_logs")
}

enum LoginType {
  PASSWORD        // 密码登录
  SMS_CODE        // 验证码登录
  LEGACY_MIGRATE  // 1.0 用户迁移登录
}

// ============================================================================
// 每日序号计数器（用于生成 accountSequence）
// ============================================================================

model DailySequenceCounter {
  id          BigInt   @id @default(autoincrement())
  dateKey     String   @unique @map("date_key")    // 格式: YYMMDD
  lastSeq     Int      @default(0) @map("last_seq")
  updatedAt   DateTime @updatedAt @map("updated_at")

  @@map("daily_sequence_counters")
}

// ============================================================================
// 发件箱（事件发布）
// ============================================================================

model OutboxEvent {
  id            BigInt      @id @default(autoincrement())
  aggregateType String      @map("aggregate_type")
  aggregateId   String      @map("aggregate_id")
  eventType     String      @map("event_type")
  payload       Json
  topic         String
  key           String
  status        OutboxStatus @default(PENDING)
  retryCount    Int         @default(0) @map("retry_count")
  maxRetries    Int         @default(3) @map("max_retries")
  lastError     String?     @map("last_error")
  publishedAt   DateTime?   @map("published_at")
  nextRetryAt   DateTime?   @map("next_retry_at")
  createdAt     DateTime    @default(now()) @map("created_at")

  @@index([status])
  @@index([nextRetryAt])
  @@map("outbox_events")
}

enum OutboxStatus {
  PENDING
  PUBLISHED
  FAILED
}

// ============================================================================
// 用户能力控制 (Capability-based permissions)
// ============================================================================

model UserCapability {
  id                BigInt     @id @default(autoincrement())
  accountSequence   String     @map("account_sequence")
  capability        String     // LOGIN, TRADING, C2C, TRANSFER_IN, TRANSFER_OUT, P2P_SEND, P2P_RECEIVE, MINING_CLAIM, KYC, PROFILE_EDIT, VIEW_ASSET, VIEW_TEAM, VIEW_RECORDS
  enabled           Boolean    @default(true)
  reason            String?    // 禁用原因
  disabledBy        String?    @map("disabled_by")   // 操作人
  disabledAt        DateTime?  @map("disabled_at")
  expiresAt         DateTime?  @map("expires_at")    // null=永久
  createdAt         DateTime   @default(now()) @map("created_at")
  updatedAt         DateTime   @updatedAt @map("updated_at")

  @@unique([accountSequence, capability])
  @@index([accountSequence])
  @@index([expiresAt])
  @@map("user_capabilities")
}

model CapabilityLog {
  id                BigInt     @id @default(autoincrement())
  accountSequence   String     @map("account_sequence")
  capability        String
  action            String     // DISABLE, ENABLE, EXPIRE
  reason            String?
  operatorId        String?    @map("operator_id")
  previousValue     Boolean    @map("previous_value")
  newValue          Boolean    @map("new_value")
  expiresAt         DateTime?  @map("expires_at")
  createdAt         DateTime   @default(now()) @map("created_at")

  @@index([accountSequence, createdAt(sort: Desc)])
  @@map("capability_logs")
}

// ============================================================================
// CDC 幂等消费追踪
// ============================================================================

// 已处理 CDC 事件表（幂等性）
// 使用 (sourceTopic, offset) 作为复合唯一键
// 这是事务性幂等消费的关键：在同一事务中插入此记录 + 执行业务逻辑
model ProcessedCdcEvent {
  id          BigInt   @id @default(autoincrement())
  sourceTopic String   @map("source_topic") @db.VarChar(200) // CDC topic 名称
  offset      BigInt   @map("offset") // Kafka offset 作为唯一标识

  tableName   String   @map("table_name") @db.VarChar(100) // 表名
  operation   String   @map("operation") @db.VarChar(10) // c/u/d/r
  processedAt DateTime @default(now()) @map("processed_at")

  @@unique([sourceTopic, offset])
  @@index([processedAt])
  @@map("processed_cdc_events")
}
