import * as bcrypt from 'bcrypt'
import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose'
import { User, UserDocument } from './user.schema'
import { JwtService } from '@nestjs/jwt'
import { EmailService } from '../email/email.service'
import { PricingService } from '../schemas/pricing.service'
import { GravityTransaction, GravityTransactionType } from '../schemas/gravity-transaction.schema'

/** 通过 IP 查询粗略地理位置（ip-api.com 免费接口） */
async function lookupIpLocation(ip: string): Promise<string> {
  if (!ip || ip === '127.0.0.1' || ip === '::1' || ip.startsWith('192.168.') || ip.startsWith('10.')) return ''
  try {
    const res = await fetch(`http://ip-api.com/json/${ip}?fields=country,regionName,city&lang=zh-CN`, { signal: AbortSignal.timeout(3000) })
    if (!res.ok) return ''
    const data: any = await res.json()
    if (data.status !== 'success') return ''
    return [data.country, data.regionName, data.city].filter(Boolean).join(' ')
  } catch { return '' }
}

// In-memory stores
const codeStore = new Map<string, { code: string; expiresAt: number }>()
const emailCodeStore = new Map<string, { code: string; expiresAt: number }>()

@Injectable()
export class UserService {
  private readonly logger = new Logger(UserService.name)

  constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,
    @InjectModel(GravityTransaction.name) private gravityTxModel: Model<GravityTransaction>,
    private jwtService: JwtService,
    private emailService: EmailService,
    private pricingService: PricingService,
  ) {}

  private async logTransaction(userId: string, amount: number, type: GravityTransactionType, description: string) {
    const u = await this.userModel.findById(userId).select('gravity').exec()
    const balance = u?.gravity ?? 0
    await this.gravityTxModel.create({ userId, amount, balance, type, description })
  }

  async sendCode(phone: string) {
    const code = process.env.NODE_ENV === 'production'
      ? String(Math.floor(100000 + Math.random() * 900000))
      : '123456'

    codeStore.set(phone, { code, expiresAt: Date.now() + 5 * 60 * 1000 })

    if (process.env.NODE_ENV !== 'production') {
      this.logger.log(`Verification code for ${phone}: ${code}`)
    }
    return { message: '验证码已发送' }
  }

  async loginByPhone(phone: string, code: string, ip?: string) {
    const record = codeStore.get(phone)
    if (!record || record.code !== code) {
      throw new HttpException('验证码错误', HttpStatus.UNAUTHORIZED)
    }
    if (Date.now() > record.expiresAt) {
      codeStore.delete(phone)
      throw new HttpException('验证码已过期', HttpStatus.UNAUTHORIZED)
    }
    codeStore.delete(phone)

    let user = await this.userModel.findOne({ phone }).exec()
    let isNew = false
    if (!user) {
      isNew = true
      user = await this.userModel.create({ phone, nickname: `用户${phone.slice(-4)}`, gravity: (await this.pricingService.getConfig()).registrationGravity })
    }

    await this.recordLogin(user._id.toString(), ip)
    if (isNew) {
      const amount = (await this.pricingService.getConfig()).registrationGravity
      await this.logTransaction(user._id.toString(), amount, 'registration', '注册赠送引力值')
    }
    return this.generateAuthResponse(user)
  }

  async loginByWx(code: string, userId?: string, ip?: string) {
    const appid = process.env.WX_APPID
    const secret = process.env.WX_SECRET
    if (!appid || !secret) {
      throw new HttpException('微信登录未配置', HttpStatus.SERVICE_UNAVAILABLE)
    }

    const wxRes = await fetch(
      `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`,
    )
    const wxData: any = await wxRes.json()

    if (wxData.errcode) {
      throw new HttpException(`微信登录失败: ${wxData.errmsg}`, HttpStatus.UNAUTHORIZED)
    }

    const openid = wxData.openid

    if (userId) {
      const user = await this.userModel.findById(userId).exec()
      if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
      if (user.wxOpenid) throw new HttpException('该账号已绑定微信', HttpStatus.CONFLICT)
      user.wxOpenid = openid
      await user.save()
      return this.generateAuthResponse(user)
    }

    let user = await this.userModel.findOne({ wxOpenid: openid }).exec()
    let isNew = false
    if (!user) {
      isNew = true
      user = await this.userModel.create({ wxOpenid: openid, nickname: '微信用户', gravity: (await this.pricingService.getConfig()).registrationGravity })
    }

    await this.recordLogin(user._id.toString(), ip)
    if (isNew) {
      const amount = (await this.pricingService.getConfig()).registrationGravity
      await this.logTransaction(user._id.toString(), amount, 'registration', '注册赠送引力值')
    }
    return this.generateAuthResponse(user)
  }

  // 📧 邮箱验证码
  async bindWxOpenid(userId: string, code: string) {
    this.logger.log(`[bindWx] userId=${userId}, code=${code ? '已提供' : '空'}`)
    const appid = process.env.WX_APPID
    const secret = process.env.WX_SECRET
    if (!appid || !secret) {
      this.logger.error(`[bindWx] 微信配置不完整`)
      throw new HttpException('微信登录未配置', HttpStatus.SERVICE_UNAVAILABLE)
    }

    const wxRes = await fetch(
      `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`,
    )
    const wxData: any = await wxRes.json()
    this.logger.log(`[bindWx] 微信接口返回: ${JSON.stringify(wxData)}`)

    if (wxData.errcode) {
      this.logger.error(`[bindWx] 微信登录失败: ${wxData.errmsg}, rid: ${wxData.rid || '无'}`)
      throw new HttpException(`微信登录失败: ${wxData.errmsg}`, HttpStatus.UNAUTHORIZED)
    }

    const openid = wxData.openid
    this.logger.log(`[bindWx] 获取到openid=${openid}`)
    const existing = await this.userModel.findOne({ wxOpenid: openid }).exec()
    if (existing) {
      this.logger.warn(`[bindWx] openid=${openid} 已绑定到其他用户 ${existing._id}`)
      throw new HttpException('该微信号已绑定其他账号', HttpStatus.CONFLICT)
    }

    const user = await this.userModel.findByIdAndUpdate(userId, { wxOpenid: openid }, { new: true }).exec()
    if (!user) { this.logger.error(`[bindWx] 用户不存在 userId=${userId}`); throw new HttpException('用户不存在', HttpStatus.NOT_FOUND) }
    this.logger.log(`[bindWx] openid=${openid} 绑定到用户 ${userId} 成功`)
    return { message: '微信绑定成功', wxOpenid: openid }
  }

  async sendEmailCode(email: string) {
    if (!email || !email.includes('@')) {
      throw new HttpException('请输入正确的邮箱地址', HttpStatus.BAD_REQUEST)
    }
    const code = String(Math.floor(100000 + Math.random() * 900000))
    emailCodeStore.set(email, { code, expiresAt: Date.now() + 10 * 60 * 1000 })
    const sent = await this.emailService.sendVerificationCode(email, code)
    if (sent) {
      return { message: '验证码已发送到邮箱' }
    }
    if (process.env.NODE_ENV !== 'production') {
      this.logger.log(`Email code for ${email}: ${code}`)
      return { message: '验证码已发送（邮件服务暂不可用，开发模式）', devCode: code }
    }
    return { message: '验证码已发送，请查收邮件' }
  }

  async loginByEmail(email: string, code: string, ip?: string) {
    const record = emailCodeStore.get(email)
    if (!record || record.code !== code) {
      throw new HttpException('验证码错误', HttpStatus.UNAUTHORIZED)
    }
    if (Date.now() > record.expiresAt) {
      emailCodeStore.delete(email)
      throw new HttpException('验证码已过期', HttpStatus.UNAUTHORIZED)
    }
    emailCodeStore.delete(email)

    // 按邮箱查找或创建用户
    let user = await this.userModel.findOne({ email }).select('+password').exec()
    let isNew = false
    if (!user) {
      isNew = true
      const nick = email.split('@')[0]
      user = await this.userModel.create({ email, nickname: nick, remaining: 0, gravity: (await this.pricingService.getConfig()).registrationGravity })
    }
    await this.recordLogin(user._id.toString(), ip)
    if (isNew) {
      const amount = (await this.pricingService.getConfig()).registrationGravity
      await this.logTransaction(user._id.toString(), amount, 'registration', '注册赠送引力值')
    }
    return { ...this.generateAuthResponse(user), isNew, hasPassword: !!user.password }
  }

  // 🔐 密码登录
  async loginByPassword(email: string, password: string, ip?: string) {
    const user = await this.userModel.findOne({ email }).select('+password').exec()
    if (!user) throw new HttpException('账号不存在', HttpStatus.NOT_FOUND)
    if (!user.password) throw new HttpException('该账号未设置密码，请使用验证码登录', HttpStatus.UNAUTHORIZED)
    const match = await bcrypt.compare(password, user.password)
    if (!match) throw new HttpException('密码错误', HttpStatus.UNAUTHORIZED)
    await this.recordLogin(user._id.toString(), ip)
    return this.generateAuthResponse(user)
  }

  // 📝 邮箱+密码注册
  async registerWithPassword(email: string, password: string, ip?: string) {
    if (!email || !email.includes('@')) {
      throw new HttpException('请输入正确的邮箱地址', HttpStatus.BAD_REQUEST)
    }
    if (!password || password.length < 6) {
      throw new HttpException('密码至少6位', HttpStatus.BAD_REQUEST)
    }
    const existing = await this.userModel.findOne({ email }).select('+password').exec()
    if (existing) {
      if (existing.password) {
        throw new HttpException('该邮箱已注册，请直接登录', HttpStatus.CONFLICT)
      }
      // 已有验证码注册的用户，补充设置密码
      existing.password = await bcrypt.hash(password, 10)
      await existing.save()
      await this.recordLogin(existing._id.toString(), ip)
      return this.generateAuthResponse(existing)
    }
    const nick = email.split('@')[0]
    const hashed = await bcrypt.hash(password, 10)
    const user = await this.userModel.create({ email, nickname: nick, password: hashed, remaining: 0, gravity: (await this.pricingService.getConfig()).registrationGravity })
    const amount = (await this.pricingService.getConfig()).registrationGravity
    await this.logTransaction(user._id.toString(), amount, 'registration', '注册赠送引力值')
    await this.recordLogin(user._id.toString(), ip)
    return this.generateAuthResponse(user)
  }

  // 🔑 已登录用户设置/修改密码
  async setPassword(userId: string, password: string) {
    if (!password || password.length < 6) {
      throw new HttpException('密码至少6位', HttpStatus.BAD_REQUEST)
    }
    const hashed = await bcrypt.hash(password, 10)
    await this.userModel.findByIdAndUpdate(userId, { password: hashed })
    return { message: '密码设置成功' }
  }

  async getInfo(userId: string) {
    const user = await this.userModel.findById(userId).exec()
    if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
    return this.safeUser(user)
  }

  async update(userId: string, data: { nickname?: string; avatar?: string }) {
    const user = await this.userModel.findByIdAndUpdate(userId, data, { new: true }).exec()
    if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
    return this.safeUser(user)
  }

  getModel() { return this.userModel }

  /** 记录登录时间/IP/归属地 */
  async recordLogin(userId: string, ip?: string) {
    const update: any = { lastLoginAt: new Date() }
    if (ip) {
      update.lastLoginIp = ip
      const location = await lookupIpLocation(ip)
      if (location) update.lastLoginLocation = location
    }
    await this.userModel.findByIdAndUpdate(userId, { $set: update }).exec()
  }

  async getUsage(userId: string) {
    const user = await this.userModel.findById(userId).exec()
    if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
    return { remaining: user.remaining, plan: user.plan, interviewCount: user.interviewCount }
  }

  async deductRemaining(userId: string) {
    const user = await this.userModel.findById(userId).exec()
    if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
    if (user.remaining <= 0) throw new HttpException('使用次数已用完', HttpStatus.FORBIDDEN)
    user.remaining -= 1
    user.interviewCount += 1
    await user.save()
  }

  async getGravityTransactions(userId: string, page = 1, limit = 20) {
    const skip = (page - 1) * limit
    const [items, total] = await Promise.all([
      this.gravityTxModel.find({ userId }).sort({ createdAt: -1 }).skip(skip).limit(limit).exec(),
      this.gravityTxModel.countDocuments({ userId }),
    ])
    return { items, total, page, limit, totalPages: Math.ceil(total / limit) }
  }

  private generateAuthResponse(user: UserDocument) {
    const payload = { userId: user._id.toString(), phone: user.phone || '', role: user.role || 'user' }
    return {
      token: this.jwtService.sign(payload),
      user: this.safeUser(user),
    }
  }

  private safeUser(user: UserDocument) {
    return {
      id: user._id.toString(),
      phone: user.phone || '',
      email: user.email || '',
      nickname: user.nickname || '',
      avatar: user.avatar || '',
      plan: user.plan,
      role: user.role || 'user',
      isSystemAdmin: user.isSystemAdmin || false,
      remaining: user.remaining,
      interviewCount: user.interviewCount,
      interviewCredits: user.interviewCredits ?? 1,
      resumeOptimizeCredits: user.resumeOptimizeCredits ?? 0,
      resumeDownloadCredits: user.resumeDownloadCredits ?? 0,
      freeOptimizeUsed: user.freeOptimizeUsed ?? 0,
      shareCredits: user.shareCredits ?? 0,
      gravity: user.gravity ?? 0,
      lastLoginAt: user.lastLoginAt,
      lastLoginIp: user.lastLoginIp,
      lastLoginLocation: user.lastLoginLocation,
    }
  }
}
