import { Inject, Injectable, InternalServerErrorException, NotFoundException, forwardRef } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { MemberType, Membership } from './entities/membership.entity'
import { Repository, LessThan } from 'typeorm'
import { MemberPlan } from './entities/memberPlan.entity'
import { addDays } from 'date-fns'
import { MemberPlanDto } from './dto/memberPlan.dto'
import { MemberOrder, MemberOrderStatus, PayMethod } from './entities/member-order.entity'
import { WeixinService } from '../weixin/weixin.service'
import { Attach, AttachType } from '../weixin/dto/attach.dto'
import { CommissionService } from '../commission/commission.service'
import { BigNumber } from 'bignumber.js'
import { PageRequest } from '../common/dto/page-request'
import { Pagination, paginate } from 'nestjs-typeorm-paginate'
import { SysConfigService } from '../sys-config/sys-config.service'

@Injectable()
export class MembershipService {
    constructor(
        @InjectRepository(Membership) private readonly memberShipRepository: Repository<Membership>,
        @InjectRepository(MemberPlan) private readonly memberPlanRepository: Repository<MemberPlan>,
        @InjectRepository(MemberOrder) private readonly memberOrderRepository: Repository<MemberOrder>,
        private readonly weixinService: WeixinService,
        @Inject(forwardRef(() => CommissionService))
        private readonly commissionService: CommissionService,
        private readonly sysConfigService: SysConfigService
    ) {}

    async findAllMembers(req: PageRequest<Membership>): Promise<Pagination<Membership>> {
        return await paginate<Membership>(this.memberShipRepository, req.page, req.search)
    }

    async findAllOrders(req: PageRequest<MemberOrder>): Promise<Pagination<MemberOrder>> {
        return await paginate<MemberOrder>(this.memberOrderRepository, req.page, req.search)
    }

    async trial(userId: number) {
        let membership = await this.memberShipRepository.findOneBy({
            userId: userId
        })
        if (membership) {
            throw new InternalServerErrorException(`User #${userId} has already been a member`)
        }
        membership = new Membership()
        membership.userId = userId
        membership.expireAt = addDays(new Date(), 3)
        membership.memberType = MemberType.Trial
        const tokenValue = (await this.sysConfigService.findByName('trial_token_value'))?.value
        membership.tokenLeft = parseInt(tokenValue)
        return await this.memberShipRepository.save(membership)
    }

    async renewMembership(userId: number, planId: number) {
        const plan = await this.memberPlanRepository.findOneBy({
            id: planId
        })
        if (!plan) {
            throw new NotFoundException(`Plan #${planId} not found`)
        }
        let membership = await this.memberShipRepository.findOneBy({
            userId: userId
        })
        if (!membership) {
            membership = new Membership()
            membership.userId = userId
        }

        if (!membership.expireAt || membership.expireAt < new Date() || MemberType.Trial == membership.memberType) {
            membership.expireAt = addDays(new Date(), plan.duration)
            membership.tokenLeft = plan.tokenLimit
        } else {
            membership.expireAt = addDays(membership.expireAt, plan.duration)
            membership.tokenLeft = Math.max(membership.tokenLeft, 0) + plan.tokenLimit
        }
        membership.planId = planId
        membership.memberType = MemberType.Paid
        await this.memberShipRepository.save(membership)
        return {
            renewed: true
        }
    }

    async getMembership(userId: number) {
        const membership = await this.memberShipRepository.findOneBy({
            userId: userId
        })
        if (!membership) {
            return await this.trial(userId)
        }
        return membership
    }

    async getPlans() {
        return await this.memberPlanRepository.find()
    }

    async savePlan(memberPlanDto: MemberPlanDto) {
        return await this.memberPlanRepository.save(memberPlanDto)
    }

    async deletePlanById(id: number) {
        const plan = await this.memberPlanRepository.findOneBy({ id })
        if (!plan) {
            throw new NotFoundException(`Plan #${id} not found`)
        }
        await this.memberPlanRepository.remove(plan)
    }

    async createOrder(userId: number, planId: number) {
        const plan = await this.memberPlanRepository.findOneBy({
            id: planId
        })
        if (!plan) {
            throw new NotFoundException(`Plan #${planId} not found`)
        }
        const order: MemberOrder = await this.memberOrderRepository.save({
            name: plan.name,
            userId: userId,
            planId: planId,
            price: plan.price
        })
        return order
    }

    async combinedJsapi(userId: number, planId: number, openid: string) {
        const order: MemberOrder = await this.createOrder(userId, planId)
        return await this.weixinService.jsapiPay(
            '会员-' + order.name,
            order.price,
            openid,
            new Attach(AttachType.MEMBER_ORDER, order.id)
        )
    }

    async orderNotify(orderId: number, transactionId: string, payMethod: PayMethod) {
        const order: MemberOrder = await this.memberOrderRepository.findOneBy({
            id: orderId
        })
        if (!order) {
            throw new NotFoundException(`Order #${orderId} not found`)
        }
        if (order.status === MemberOrderStatus.CANCEL) {
            throw new NotFoundException(`Order #${orderId} has been canceled`)
        }
        if (order.status === MemberOrderStatus.FINISH) {
            return
        }
        order.status = MemberOrderStatus.FINISH
        order.finishTime = new Date()
        order.transactionId = transactionId
        order.payMethod = payMethod
        await this.memberOrderRepository.save(order)
        await this.renewMembership(order.userId, order.planId)
        await this.commissionService.doCommission(order.userId, new BigNumber(order.price))
    }

    async saveUsage(userId: number, usage: number) {
        const membership = await this.memberShipRepository.findOneBy({
            userId: userId
        })
        if (!membership) {
            throw new NotFoundException(`Membership #${userId} not found`)
        }
        membership.tokenLeft = membership.tokenLeft - usage
        await this.memberShipRepository.update(
            {
                userId
            },
            {
                tokenLeft: () => `tokenLeft - ${usage}`
            }
        )
    }
}
