import { HttpException, Injectable, InternalServerErrorException, Logger, OnModuleInit } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { PaperOrder, PaperOrderStatus } from './entities/paper-order.entity'
import { Repository, Like, Between } from 'typeorm'
import { PaperGenResult } from './entities/paper-gen-result.entity'
import { Pagination, paginate } from 'nestjs-typeorm-paginate'
import { PageRequest } from 'src/common/dto/page-request'
import { CreatePaperOrderDto } from './dto/create-order.dto'
import { genPaper } from './paper-gen'
import { genGeneralPaper1 } from './paper-gen/general'
import { createLLM } from './paper-gen/llm'
import { genChapters } from './paper-gen/chapter'
import { HumanMessage } from 'langchain/schema'
import axios from 'axios'
import * as qs from 'qs'
import { load } from 'cheerio'
import { parse } from 'date-fns'
import { RefSearchRecord } from './entities/ref-search-record.entity'
import { MembershipService } from 'src/membership/membership.service'
import { startOfDay, endOfDay } from 'date-fns'
import { MemberType } from 'src/membership/entities/membership.entity'
import { PaperOrderAttachment } from './entities/paper-order-attachment.entity'

@Injectable()
export class PaperService implements OnModuleInit {
    constructor(
        @InjectRepository(PaperOrder)
        private readonly paperOrderRepository: Repository<PaperOrder>,
        @InjectRepository(PaperGenResult)
        private readonly paperGenResultRepository: Repository<PaperGenResult>,
        @InjectRepository(RefSearchRecord)
        private readonly refSearchRecordRepository: Repository<RefSearchRecord>,
        private readonly membershipService: MembershipService,
        @InjectRepository(PaperOrderAttachment)
        private readonly paperOrderAttachmentRepository: Repository<PaperOrderAttachment>
    ) {}

    async onModuleInit() {
        for (const order of await this.paperOrderRepository.findBy({
            status: PaperOrderStatus.Generating
        })) {
            if (
                (await this.paperGenResultRepository.countBy({
                    orderId: order.id
                })) === 0
            ) {
                order.status = PaperOrderStatus.Pending
            } else {
                order.status = PaperOrderStatus.Complete
            }
            await this.paperOrderRepository.save(order)
        }
    }

    async findAllOrders(req, pageReq: PageRequest<PaperOrder>) {
        ;(pageReq as any).search.where = (pageReq as any).search.where || {}
        let where = (pageReq as any).search.where
        if (!req.user.roles.includes('paperGen')) {
            where.userId = req.user.id
        }
        if (where.title) {
            where.title = Like(`%${where.title}%`)
        }
        return await paginate<PaperOrder>(this.paperOrderRepository, pageReq.page, pageReq.search)
    }

    async findOrderById(id: number): Promise<PaperOrder> {
        return await this.paperOrderRepository.findOneByOrFail({
            id
        })
    }

    async findAllResults(req: PageRequest<PaperGenResult>) {
        return await paginate<PaperGenResult>(this.paperGenResultRepository, req.page, req.search)
    }

    async createOrder(dto: CreatePaperOrderDto) {
        return await this.paperOrderRepository.save(dto)
    }

    async updateOrder(id: number, data) {
        return await this.paperOrderRepository.update(id, data)
    }

    async gen(orderId: number) {
        const order = await this.findOrderById(orderId)
        if (order.status === PaperOrderStatus.Generating) {
            throw new InternalServerErrorException('正在生成中，请稍后再试')
        }
        order.status = PaperOrderStatus.Generating
        await this.paperOrderRepository.save(order)
        this.genPaper(order)
    }

    async genChapters(id: number, description: string) {
        const order = await this.findOrderById(id)
        const tools = createLLM()
        const chapters = await genChapters(tools, order.major, order.title, description)

        order.chapters = chapters
        order.description = description
        await this.paperOrderRepository.save(order)

        return chapters
    }

    async genPaper(order: PaperOrder) {
        try {
            let content, duration, tokenUsage, fileUrl
            if (/计算机/.test(order.major)) {
                ;({ content, duration, tokenUsage, fileUrl } = await genPaper(order.title, order.description))
            } else {
                ;({ content, duration, tokenUsage, fileUrl } = await genGeneralPaper1(
                    order.major,
                    order.title,
                    order.description,
                    order.chapters
                ))
            }
            await this.paperGenResultRepository.save({
                orderId: order.id,
                content,
                duration,
                tokenUsage,
                fileUrl
            })
            order.status = PaperOrderStatus.Complete
            await this.paperOrderRepository.save(order)
        } catch (error) {
            Logger.error('生成论文失败', error.stack)
            order.status = PaperOrderStatus.Pending
            await this.paperOrderRepository.save(order)
        }
    }

    async searchReferences(userId: number, title: string) {
        let record = await this.refSearchRecordRepository.findOneBy({
            userId,
            createdAt: Between(startOfDay(new Date()), endOfDay(new Date()))
        })
        if (record) {
            const member = await this.membershipService.getMembership(userId, false)
            if (!member || member.isExpired || member.memberType === MemberType.Trial) {
                throw new InternalServerErrorException('USAGE_LIMIT_EXCEEDED')
            }
        }

        // const { llm } = createLLM()
        // const { content } = await llm.call([
        //     new HumanMessage(`你要从我给你的论文标题中提取2-3个由逗号分隔的主要关键词。\n\n标题：${title}\n关键词：`)
        // ])
        // const keywords = content.replace(/，/g, ',').split(',')
        const keywords = [title]
        const http = axios.create({
            headers: {
                Host: 'kns.cnki.net',
                'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
                'X-Requested-With': 'XMLHttpRequest',
                'sec-ch-ua-mobile': '?0',
                'User-Agent':
                    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
                'sec-ch-ua-platform': 'macOS',
                Origin: 'https://kns.cnki.net',
                'Sec-Fetch-Site': 'same-origin',
                'Sec-Fetch-Mode': 'cors',
                'Sec-Fetch-Dest': 'empty',
                Referer:
                    'https://kns.cnki.net/kns8s/defaultresult/index?crossids=YSTT4HG0%2CLSTPFY1C%2CJUP3MUPD%2CMPMFIG1A%2CWQ0UVIAA%2CBLZOG7CK%2CEMRPGLPA%2CPWFIRAGL%2CNLBO1Z6R%2CNN3FJMUV&korder=SU&kw=%E9%98%BF%E6%96%AF%E9%A1%BF',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,und;q=0.6',
                Cookie: 'Ecp_notFirstLogin=8TNR2E; KNS3COOKIE=1705297590.529.38190.813537|b25e41a932fd162af3b8c5cff4059fc3; Ecp_ClientId=m240115134600532586; Ecp_IpLoginFail=240115180.111.242.2; SID_sug=128008; SID_kns_new=kns25128005; Ecp_ClientIp=180.111.242.2; SID_restapi=018105; LID=WEEvREcwSlJHSldTTEYyTE5mZ3VrVmtob3VPdjBpS29QOWl1NG1pRVJqWT0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4IQMovwHtwkF4VYPoHbKxJw!!; Ecp_session=1; Ecp_showrealname=1; Ecp_LoginStuts={"IsAutoLogin":false,"UserName":"15077886171","ShowName":"15077886171","UserType":"jf","BUserName":"","BShowName":"","BUserType":"","r":"8TNR2E","Members":[]}; Ecp_loginuserjf=15077886171; c_m_LinID=LinID=WEEvREcwSlJHSldTTEYyTE5mZ3VrVmtob3VPdjBpS29QOWl1NG1pRVJqWT0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4IQMovwHtwkF4VYPoHbKxJw!!&ot=02%2F14%2F2024%2013%3A52%3A01; c_m_expire=2024-02-14%2013%3A52%3A01; dblang=both'
            }
        })

        let en = []
        let zh = []
        for (let kw of keywords) {
            const res = await http.post(
                'https://kns.cnki.net/kns8s/brief/grid',
                qs.stringify({
                    boolSearch: true,
                    QueryJson: `{"Platform":"","Resource":"CROSSDB","Classid":"WD0FTY92","Products":"","QNode":{"QGroup":[{"Key":"Subject","Title":"","Logic":0,"Items":[{"Field":"SU","Value":"${kw}","Operator":"TOPRANK","Logic":0}],"ChildItems":[]}]},"ExScope":1,"SearchType":2,"Rlang":"CHINESE","KuaKuCode":"YSTT4HG0,LSTPFY1C,JUP3MUPD,MPMFIG1A,EMRPGLPA,WQ0UVIAA,BLZOG7CK,PWFIRAGL,NN3FJMUV,NLBO1Z6R"}`,
                    pageNum: 1,
                    pageSize: 20,
                    dstyle: 'listmode',
                    boolSortSearch: false,
                    productStr:
                        'YSTT4HG0,LSTPFY1C,RMJLXHZ3,JQIRZIYA,JUP3MUPD,1UR4K4HZ,BPBAFJ5S,R79MZMCB,MPMFIG1A,EMRPGLPA,J708GVCE,ML4DRIDX,WQ0UVIAA,NB3BWEHK,XVLO76FD,HR1YT1Z9,BLZOG7CK,PWFIRAGL,NN3FJMUV,NLBO1Z6R,',
                    aside: `主题：${kw}`,
                    searchFrom: '资源范围：总库',
                    CurPage: 1
                })
            )
            zh = zh.concat(this.parseRefHtml(res.data))

            const res1 = await http.post(
                'https://kns.cnki.net/kns8s/brief/grid',
                qs.stringify({
                    boolSearch: true,
                    QueryJson: `{"Platform":"","Resource":"CROSSDB","Classid":"WD0FTY92","Products":"","QNode":{"QGroup":[{"Key":"Subject","Title":"","Logic":0,"Items":[{"Field":"SU","Value":"${kw}","Operator":"TOPRANK","Logic":0}],"ChildItems":[]},{"Key":"SCDBGroup","Title":"","Logic":0,"Items":[],"ChildItems":[{"Key":"LG","Title":"","Logic":0,"Items":[{"Key":"EN","Title":"英语","Logic":1,"Field":"LG","Operator":"DEFAULT","Value":"EN","Value2":"","Name":"LG","ExtendType":0}],"ChildItems":[]}]}]},"ExScope":1,"SearchType":6,"Rlang":"FOREIGN","KuaKuCode":"YSTT4HG0,LSTPFY1C,JUP3MUPD,MPMFIG1A,WQ0UVIAA,BLZOG7CK,EMRPGLPA,PWFIRAGL,NLBO1Z6R,NN3FJMUV","View":""}`,
                    pageNum: 1,
                    pageSize: 20,
                    dstyle: 'listmode',
                    boolSortSearch: false,
                    productStr:
                        'YSTT4HG0,LSTPFY1C,RMJLXHZ3,JQIRZIYA,JUP3MUPD,1UR4K4HZ,BPBAFJ5S,R79MZMCB,MPMFIG1A,WQ0UVIAA,NB3BWEHK,XVLO76FD,HR1YT1Z9,BLZOG7CK,EMRPGLPA,J708GVCE,ML4DRIDX,PWFIRAGL,NLBO1Z6R,NN3FJMUV,',
                    aside: `主题：${kw}`,
                    searchFrom: '资源范围：总库',
                    CurPage: 1
                })
            )
            en = en.concat(this.parseRefHtml(res1.data))
        }
        zh = this.sortDeduplication(zh.slice(0, 20))
        en = this.sortDeduplication(en.slice(0, 10))

        async function getRef(arr) {
            await Promise.all(
                arr.map((item) => {
                    return http
                        .post(
                            'https://kns.cnki.net/dm8/API/GetExport',
                            qs.stringify({
                                filename: item.filename,
                                displaymode: 'GBTREFER,MLA,APA',
                                uniplatform: 'NZKPT'
                            })
                        )
                        .then((res) => {
                            item.ref = res.data.data.map((i) => i.value[0].replace(/^\[\d+\]/, ''))
                        })
                })
            )
        }
        await getRef(zh)
        await getRef(en)
        await this.refSearchRecordRepository.save({
            userId,
            title
        })
        return { zh, en }
    }

    parseRefHtml(html: any) {
        const $ = load(html)
        const trs = $('tr')
        const res = []
        trs.each((i, tr) => {
            let $$ = load(tr)
            const filename = $$('td.seq .cbItem').attr('value')
            if (filename) {
                const name = $$('td.name a').text()
                const date = $$('td.date').text()
                res.push({
                    filename,
                    name,
                    date
                })
            }
        })
        return res
    }

    sortDeduplication(arr: any[]) {
        let res = []
        arr.forEach((item) => {
            if (!res.find((i) => i.filename === item.filename)) {
                res.push(item)
            }
        })
        res.sort((a, b) => {
            return parse(b.date, 'yyyy-MM-dd', new Date()).getTime() - parse(a.date, 'yyyy-MM-dd', new Date()).getTime()
        })
        return res
    }

    async attachments(req: PageRequest<PaperOrderAttachment>) {
        return await paginate<PaperOrderAttachment>(this.paperOrderAttachmentRepository, req.page, req.search)
    }

    async createAttachment(dto: PaperOrderAttachment) {
        return await this.paperOrderAttachmentRepository.save(dto)
    }

    async deleteAttachment(id: number) {
        return await this.paperOrderAttachmentRepository.delete(id)
    }
}
