import { Inject, Injectable, forwardRef } from '@nestjs/common'
import { FindManyOptions, FindOptionsOrder, In, Repository, UpdateResult } from 'typeorm'
import { InjectRepository } from '@nestjs/typeorm'
import { paginate, Pagination } from 'nestjs-typeorm-paginate'
import { PageRequest } from '../common/dto/page-request'
import { Comment } from './entities/comment.entity'
import { CommentDto } from './dto/comment.dto'
import { LikesService } from 'src/likes/likes.service'
import { Likes } from 'src/likes/entities/likes.entity'
import { Moments } from 'src/moments/entities/moments.entity'
import { ConditionService } from 'src/condition/condition.service'

@Injectable()
export class CommentService {
    constructor(
        @InjectRepository(Comment)
        private readonly commentRepository: Repository<Comment>,
        @InjectRepository(Likes)
        private readonly likesRepository: Repository<Likes>,
        @InjectRepository(Moments)
        private readonly momentsRepository: Repository<Moments>,
        private readonly likesService: LikesService,
        @Inject(forwardRef(() => ConditionService))
        private readonly conditionService: ConditionService
    ) {}

    async findAll(req: PageRequest<Comment>, userId: number): Promise<Pagination<Comment>> {
        const query = await this.commentRepository
            .createQueryBuilder()
            .where('comment.isDel = false')
            .andWhere('rootCommentId is null')

        if (req.search) {
            const searchOptions = req.search as FindManyOptions<Comment>
            if ('where' in req.search) {
                if ('where' in searchOptions && searchOptions.where) {
                    for (const [key, value] of Object.entries(searchOptions.where)) {
                        query.andWhere(`comment.${key} = ${value}`)
                    }
                }
            }

            query.orderBy('comment.isTop', 'DESC')

            if ('order' in req.search) {
                if ('order' in searchOptions && searchOptions.order) {
                    for (const [key, value] of Object.entries(searchOptions.order)) {
                        query.addOrderBy(`comment.${key}`, value as 'ASC' | 'DESC')
                    }
                }
            }
        }

        const options = { ...req.page }
        const page = await paginate<Comment>(query, options)

        const { items, meta } = page
        const rootMap = new Map<number, Comment>()
        const ids: number[] = []

        items.forEach((comment) => {
            rootMap.set(comment.id, comment)
            ids.push(comment.id)
        })

        const childList = await this.commentRepository
            .createQueryBuilder()
            .where({
                rootCommentId: In(ids)
            })
            .andWhere(
                `(
                SELECT COUNT(*)
                FROM comment AS c2
                WHERE c2.rootCommentId = comment.rootCommentId AND c2.createdAt <= comment.createdAt
            ) <= 2`
            )
            .orderBy('comment.rootCommentId, comment.createdAt', 'ASC')
            .getMany()

        childList.forEach((comment) => {
            ids.push(comment.id)
        })

        const likes = await this.likesRepository
            .createQueryBuilder('likes')
            .select('likes.targetId')
            .where({
                userId: +userId,
                targetId: In(ids),
                type: 2,
                isValid: true
            })
            .getMany()
        const targetIds: number[] = likes.map((like) => like.targetId)

        childList.forEach((comment) => {
            if (targetIds.includes(comment.id)) {
                comment.isLiked = true
            } else {
                comment.isLiked = false
            }
            if (Boolean(comment.rootCommentId)) {
                const root = rootMap.get(comment.rootCommentId)
                if (root.hasOwnProperty('childList')) {
                    root.childList.push(comment)
                } else {
                    const childList: Comment[] = []
                    childList.push(comment)
                    root.childList = childList
                }
                rootMap.set(root.id, root)
            }
        })

        const newItems = Array.from(rootMap.values())
        newItems.forEach((comment) => {
            comment.isLiked = targetIds.includes(comment.id) ? true : false
        })

        const result: Pagination<Comment> = {
            items: newItems,
            meta
        }

        return result
    }

    public async create(commentDto: CommentDto) {
        if (commentDto.rootCommentId) {
            const list: CommentDto[] = []
            const root = await this.commentRepository.findOneBy({ id: commentDto.toCommentId })
            commentDto.replyUserName = root.userName
            root.replyNum += 1
            list.push(root)
            if (commentDto.rootCommentId != commentDto.toCommentId) {
                const root = await this.commentRepository.findOneBy({ id: commentDto.rootCommentId })
                root.replyNum += 1
                list.push(root)
            }
            await this.commentRepository.save(list)
        }
        const moments = await this.momentsRepository.findOneBy({ id: +commentDto.momentsId })
        moments.commentNum += 1
        await this.momentsRepository.save(moments)
        const newComment = await this.commentRepository.save(commentDto)

        if (false) {
            await this.conditionService.reply(newComment, moments)
        }

        return newComment
    }

    public async findChildList(req: PageRequest<Comment>): Promise<Pagination<Comment>> {
        const query = await this.commentRepository.createQueryBuilder().where('comment.isDel = false')

        if (req.search) {
            const searchOptions = req.search as FindManyOptions<Comment>
            if ('where' in req.search) {
                if ('where' in searchOptions && searchOptions.where) {
                    for (const [key, value] of Object.entries(searchOptions.where)) {
                        query.andWhere(`comment.${key} = ${value}`)
                    }
                }
            }

            if ('order' in req.search) {
                if ('order' in searchOptions && searchOptions.order) {
                    for (const [key, value] of Object.entries(searchOptions.order)) {
                        query.addOrderBy(`comment.${key}`, value as 'ASC' | 'DESC')
                    }
                }
            }
        }

        return await paginate<Comment>(query, { ...req.page })
    }

    public async delete(id: number): Promise<void> {
        const comment = await this.commentRepository.findOneBy({ id: +id })
        if (comment) {
            comment.isDel = true
            await this.commentRepository.save(comment)
        }
        const moments = await this.momentsRepository.findOneBy({ id: +comment.momentsId })
        moments.commentNum -= 1
        await this.momentsRepository.save(moments)
    }

    public async like(id: number, userId: number): Promise<void> {
        const comment = await this.commentRepository.findOneBy({ id: +id })
        if (comment) {
            const likes = await this.likesService.like(id, userId, 2)
            if (likes.isValid) {
                comment.commentLikeCount += 1
            } else {
                comment.commentLikeCount -= 1
            }
            await this.commentRepository.save(comment)
        }
    }
}
