import * as crypto from 'crypto'
import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model, Types } from 'mongoose'
import { ShareRecord, ShareRecordDocument, ShareVisit, ShareVisitDocument } from './share.schema'
import { QuotaService } from '../user/quota.service'
import { User, UserDocument } from '../user/user.schema'

const DAILY_LIMIT = 3
const LINK_TTL_DAYS = 30

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

  constructor(
    @InjectModel(ShareRecord.name) private shareModel: Model<ShareRecordDocument>,
    @InjectModel(ShareVisit.name) private visitModel: Model<ShareVisitDocument>,
    @InjectModel(User.name) private userModel: Model<UserDocument>,
    private quotaService: QuotaService,
  ) {}

  async create(userId: string, body: { type: string; refId?: string; title?: string; description?: string }) {
    const shareCode = crypto.randomBytes(4).toString('hex')
    const record = await this.shareModel.create({
      userId: new Types.ObjectId(userId),
      shareCode,
      type: body.type || 'app',
      refId: body.refId || '',
      title: body.title || '我在职引发现了好东西',
      description: body.description || '快来一起体验吧',
    })
    return {
      shareCode: record.shareCode,
      shareUrl: `/share/${record.shareCode}`,
      wechatShareInfo: {
        title: record.title,
        description: record.description,
        path: `/pages/share/share?code=${record.shareCode}`,
      },
    }
  }

  async visit(shareCode: string, visitorId: string, visitorUserId?: string) {
    const share = await this.shareModel.findOne({ shareCode, isActive: true }).exec()
    if (!share) throw new HttpException('分享链接不存在或已失效', HttpStatus.NOT_FOUND)

    await this.shareModel.findByIdAndUpdate(share._id, { $inc: { visitCount: 1 } }).exec()

    const sharerIdStr = share.userId.toString()
    if (!visitorUserId || visitorUserId === sharerIdStr) {
      return { sharer: true, visitorUserId }
    }

    const existing = await this.visitModel.findOne({
      shareId: share._id,
      visitorId,
    }).exec()
    if (existing) return { alreadyVisited: true, visitorUserId }

    await this.visitModel.create({
      shareId: share._id,
      sharerId: share.userId,
      visitorId,
      visitorUserId: new Types.ObjectId(visitorUserId),
    }).catch(() => {})

    const alreadyCredited = await this.visitModel.findOne({
      shareId: share._id,
      visitorId,
      credited: true,
    }).exec()
    if (alreadyCredited) return { credited: true, visitorUserId }

    const todayStart = new Date()
    todayStart.setHours(0, 0, 0, 0)

    const todayCredited = await this.visitModel.countDocuments({
      sharerId: share.userId,
      credited: true,
      creditedAt: { $gte: todayStart },
    }).exec()

    if (todayCredited >= DAILY_LIMIT) return { dailyLimitReached: true, visitorUserId }

    try {
      await this.quotaService.grantGravity(sharerIdStr, 1)
    } catch (e) {
      return { creditFailed: true, visitorUserId }
    }

    await this.visitModel.updateOne(
      { shareId: share._id, visitorId },
      { $set: { credited: true, creditedAt: new Date() } },
    ).exec()

    await this.shareModel.findByIdAndUpdate(share._id, { $inc: { creditedCount: 1 } }).exec()

    return { credited: true, visitorUserId }
  }

  async stats(userId: string) {
    const todayStart = new Date()
    todayStart.setHours(0, 0, 0, 0)

    const [totalShares, visitAgg, todayAgg, user] = await Promise.all([
      this.shareModel.countDocuments({ userId: new Types.ObjectId(userId) }).exec(),
      this.visitModel.aggregate([
        { $match: { sharerId: new Types.ObjectId(userId) } },
        {
          $group: {
            _id: null,
            totalVisits: { $sum: 1 },
            creditedCount: { $sum: { $cond: ['$credited', 1, 0] } },
          },
        },
      ]).exec(),
      this.visitModel.countDocuments({
        sharerId: new Types.ObjectId(userId),
        credited: true,
        creditedAt: { $gte: todayStart },
      }).exec(),
      this.userModel.findById(userId).exec(),
    ])

    return {
      totalShares,
      totalVisits: visitAgg[0]?.totalVisits ?? 0,
      creditedCount: visitAgg[0]?.creditedCount ?? 0,
      todayCredited: todayAgg,
      gravity: user?.gravity ?? 0,
    }
  }

  async records(userId: string, page = 1, pageSize = 20) {
      const list = await this.shareModel.find({ userId: new Types.ObjectId(userId) })
        .sort({ createdAt: -1 })
        .skip((page - 1) * pageSize)
        .limit(pageSize)
        .exec()
      const total = await this.shareModel.countDocuments({ userId: new Types.ObjectId(userId) }).exec()
      return {
        list: list.map(r => ({
          shareCode: r.shareCode,
          type: r.type,
          title: r.title,
          visitCount: r.visitCount,
          creditedCount: r.creditedCount,
          createdAt: r.createdAt,
        })),
      total,
      page,
      pageSize,
    }
  }

  async visitors(userId: string, page = 1, pageSize = 20) {
    const [list, total] = await Promise.all([
      this.visitModel.aggregate([
        { $match: { sharerId: new Types.ObjectId(userId) } },
        { $sort: { createdAt: -1 } },
        { $skip: (page - 1) * pageSize },
        { $limit: pageSize },
        {
          $lookup: {
            from: 'users',
            localField: 'visitorUserId',
            foreignField: '_id',
            as: 'visitorUser',
          },
        },
        { $unwind: { path: '$visitorUser', preserveNullAndEmptyArrays: true } },
        {
          $project: {
            _id: 0,
            visitorId: 1,
            credited: 1,
            creditedAt: 1,
            createdAt: 1,
            nickname: { $ifNull: ['$visitorUser.nickname', '匿名用户'] },
            avatar: { $ifNull: ['$visitorUser.avatar', ''] },
          },
        },
      ]).exec(),
      this.visitModel.countDocuments({ sharerId: new Types.ObjectId(userId) }).exec(),
    ])
    return { list, total, page, pageSize }
  }
}
