import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'
import { PrismaService } from 'src/providers/prisma/prisma.service'
import { UserId } from 'src/common/types/user-id.type'
import { RealtimeGateway } from '../realtime/realtime.gateway'
import { SocketEvent } from 'src/common/socket/socket-events'

@Injectable()
export class KanbanService {
    constructor(private readonly prisma: PrismaService, private readonly realtime: RealtimeGateway) { }

    getBoards(userId: UserId) {
        return this.prisma.kanbanBoard.findMany({
            where: {
                OR: [
                    { ownerId: userId },
                    {
                        messages: {
                            some: {
                                conversation: {
                                    members: { some: { userId } }
                                }
                            }
                        }
                    },
                    {
                        columns: {
                            some: {
                                tasks: {
                                    some: {
                                        messages: {
                                            some: {
                                                conversation: {
                                                    members: { some: { userId } }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                ]
            },
            include: { columns: { include: { tasks: { orderBy: { position: 'asc' } } }, orderBy: { position: 'asc' } } },
            orderBy: { updatedAt: 'desc' }
        })
    }

    async createBoard(userId: UserId, title: string) {
        this.checkTitle(title)
        return this.prisma.kanbanBoard.create({
            data: {
                ownerId: userId, title: title.trim(), createdAt: Date.now(), updatedAt: Date.now(),
                columns: { create: [{ title: 'Нужно сделать', position: 0 }, { title: 'В работе', position: 1 }, { title: 'Готово', position: 2 }] }
            },
            include: { columns: { include: { tasks: true }, orderBy: { position: 'asc' } } }
        })
    }

    async updateBoard(userId: UserId, boardId: number, title: string) {
        await this.requireBoardAccess(userId, boardId)
        this.checkTitle(title)
        await this.prisma.kanbanBoard.update({ where: { id: boardId }, data: { title: title.trim(), updatedAt: Date.now() } })
        return this.emitBoard(boardId)
    }

    async deleteBoard(userId: UserId, boardId: number) {
        await this.requireBoardAccess(userId, boardId)
        await this.prisma.kanbanBoard.delete({ where: { id: boardId } })
    }

    async createColumn(userId: UserId, boardId: number, title: string) {
        await this.requireBoardAccess(userId, boardId)
        this.checkTitle(title)
        const position = await this.prisma.kanbanColumn.count({ where: { boardId } })
        await this.prisma.kanbanColumn.create({ data: { boardId, title: title.trim(), position } })
        return this.touchAndEmit(boardId)
    }

    async createTask(userId: UserId, columnId: number, title: string, description?: string) {
        const column = await this.requireColumnAccess(userId, columnId)
        this.checkTitle(title)
        const position = await this.prisma.kanbanTask.count({ where: { columnId } })
        await this.prisma.kanbanTask.create({ data: { columnId, title: title.trim(), description: description?.trim(), position, createdAt: Date.now(), updatedAt: Date.now() } })
        return this.touchAndEmit(column.boardId)
    }

    async updateTask(userId: UserId, taskId: number, data: { title?: string; description?: string; columnId?: number; position?: number }) {
        const task = await this.requireTaskAccess(userId, taskId)
        if (data.title !== undefined) this.checkTitle(data.title)
        if (data.position !== undefined && data.position < 0) throw new BadRequestException('Invalid position')
        if (data.columnId !== undefined) {
            const target = await this.requireColumnAccess(userId, data.columnId)
            if (target.boardId !== task.column.boardId) throw new BadRequestException('Cannot move a task to another board')
        }
        await this.prisma.kanbanTask.update({
            where: { id: taskId },
            data: { title: data.title?.trim(), description: data.description?.trim(), columnId: data.columnId, position: data.position, updatedAt: Date.now() }
        })
        return this.touchAndEmit(task.column.boardId)
    }

    async deleteTask(userId: UserId, taskId: number) {
        const task = await this.requireTaskAccess(userId, taskId)
        await this.prisma.kanbanTask.delete({ where: { id: taskId } })
        return this.touchAndEmit(task.column.boardId)
    }

    private async touchAndEmit(boardId: number) {
        await this.prisma.kanbanBoard.update({ where: { id: boardId }, data: { updatedAt: Date.now() } })
        return this.emitBoard(boardId)
    }

    private async emitBoard(boardId: number) {
        const board = await this.prisma.kanbanBoard.findUnique({
            where: { id: boardId },
            include: { columns: { include: { tasks: { orderBy: { position: 'asc' } } }, orderBy: { position: 'asc' } } }
        })
        if (!board) throw new NotFoundException('Board not found')
        const members = await this.prisma.conversationMember.findMany({
            where: { conversation: { messages: { some: { OR: [{ kanbanBoardId: boardId }, { kanbanTask: { column: { boardId } } }] } } } },
            select: { userId: true },
            distinct: ['userId']
        })
        const recipients = new Set<bigint>([board.ownerId, ...members.map(member => member.userId)])
        recipients.forEach(id => this.realtime.sendToUser(UserId(id), SocketEvent.KANBAN_UPDATE, board))
        return board
    }

    private async requireBoard(userId: UserId, boardId: number) {
        const board = await this.prisma.kanbanBoard.findUnique({ where: { id: boardId } })
        if (!board) throw new NotFoundException('Board not found')
        if (board.ownerId !== userId) throw new ForbiddenException()
        return board
    }

    private async requireColumn(userId: UserId, columnId: number) {
        const column = await this.prisma.kanbanColumn.findUnique({ where: { id: columnId }, include: { board: true } })
        if (!column) throw new NotFoundException('Column not found')
        if (column.board.ownerId !== userId) throw new ForbiddenException()
        return column
    }

    private async requireBoardAccess(userId: UserId, boardId: number) {
        const board = await this.prisma.kanbanBoard.findFirst({
            where: {
                id: boardId,
                OR: [
                    { ownerId: userId },
                    { messages: { some: { conversation: { members: { some: { userId } } } } } },
                    {
                        columns: {
                            some: {
                                tasks: {
                                    some: {
                                        messages: {
                                            some: { conversation: { members: { some: { userId } } } }
                                        }
                                    }
                                }
                            }
                        }
                    }
                ]
            }
        })
        if (!board) throw new ForbiddenException('Kanban board is not available')
        return board
    }

    private async requireColumnAccess(userId: UserId, columnId: number) {
        const column = await this.prisma.kanbanColumn.findUnique({ where: { id: columnId } })
        if (!column) throw new NotFoundException('Column not found')
        await this.requireBoardAccess(userId, column.boardId)
        return column
    }

    private async requireTaskAccess(userId: UserId, taskId: number) {
        const task = await this.prisma.kanbanTask.findUnique({ where: { id: taskId }, include: { column: { include: { board: true } } } })
        if (!task) throw new NotFoundException('Task not found')
        await this.requireBoardAccess(userId, task.column.boardId)
        return task
    }

    private checkTitle(title: string) {
        if (!title?.trim() || title.trim().length > 120) throw new BadRequestException('Invalid title')
    }
}
