import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { ParentType, Trimester, UserRole } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { UpsertParentIndicesDto } from './dto/upsert-parent-indices.dto';
import { UpsertBabyIndicesDto, UpsertBabyTrimesterDto } from './dto/upsert-baby-indices.dto';

const MAX_PHOTOS_PER_SECTION = 5;

@Injectable()
export class IndicesService {
  constructor(private readonly prisma: PrismaService) {}

  private async assertAccess(
    userId: string,
    projectId: string,
    options: { adminOnly?: boolean } = {},
  ) {
    const [user, project, membership] = await Promise.all([
      this.prisma.user.findUnique({
        where: { id: userId },
        select: { id: true, role: true, workspaceId: true },
      }),
      this.prisma.project.findUnique({
        where: { id: projectId },
        select: { id: true, workspaceId: true },
      }),
      this.prisma.projectMembership.findUnique({
        where: { userId },
        select: { projectId: true },
      }),
    ]);

    if (!user) throw new NotFoundException('Utilisateur introuvable');
    if (!project) throw new NotFoundException('Projet introuvable');
    if (!user.workspaceId || user.workspaceId !== project.workspaceId) {
      throw new ForbiddenException('Acces refuse a ce projet');
    }

    if (user.role === UserRole.ADMIN) return { user, isAdmin: true };
    if (options.adminOnly) throw new ForbiddenException('Action reservee aux admins');
    if (!membership || membership.projectId !== projectId) {
      throw new ForbiddenException('Vous n etes pas rattache a ce projet');
    }

    return { user, isAdmin: false };
  }

  async getProjectIndices(userId: string, projectId: string) {
    await this.assertAccess(userId, projectId);

    const [parentIndices, babyIndices] = await Promise.all([
      this.prisma.parentIndices.findMany({
        where: { projectId },
        include: {
          photos: { orderBy: { sortOrder: 'asc' } },
        },
        orderBy: { parentType: 'asc' },
      }),
      this.prisma.babyIndices.findMany({
        where: { projectId },
        include: {
          trimesters: {
            include: {
              photos: { orderBy: { sortOrder: 'asc' } },
            },
            orderBy: { trimester: 'asc' },
          },
        },
        orderBy: { babyIndex: 'asc' },
      }),
    ]);

    return { parentIndices, babyIndices };
  }

  async upsertParentIndices(
    userId: string,
    projectId: string,
    parentType: ParentType,
    dto: UpsertParentIndicesDto,
  ) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    return this.prisma.parentIndices.upsert({
      where: { projectId_parentType: { projectId, parentType } },
      create: {
        projectId,
        parentType,
        poids: dto.poids ?? null,
        taille: dto.taille ?? null,
        perimCranien: dto.perimCranien ?? null,
        dateNaissance: dto.dateNaissance ? new Date(dto.dateNaissance) : null,
      },
      update: {
        poids: dto.poids ?? null,
        taille: dto.taille ?? null,
        perimCranien: dto.perimCranien ?? null,
        dateNaissance: dto.dateNaissance ? new Date(dto.dateNaissance) : null,
      },
      include: { photos: { orderBy: { sortOrder: 'asc' } } },
    });
  }

  async addParentPhoto(userId: string, projectId: string, parentType: ParentType, url: string) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    const parentIndices = await this.prisma.parentIndices.upsert({
      where: { projectId_parentType: { projectId, parentType } },
      create: { projectId, parentType },
      update: {},
      select: { id: true },
    });

    const count = await this.prisma.parentIndexPhoto.count({
      where: { parentIndicesId: parentIndices.id },
    });

    if (count >= MAX_PHOTOS_PER_SECTION) {
      throw new BadRequestException(`Maximum ${MAX_PHOTOS_PER_SECTION} photos par section`);
    }

    return this.prisma.parentIndexPhoto.create({
      data: { parentIndicesId: parentIndices.id, url, sortOrder: count },
    });
  }

  async deleteParentPhoto(userId: string, projectId: string, parentType: ParentType, photoId: string) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    const photo = await this.prisma.parentIndexPhoto.findUnique({
      where: { id: photoId },
      include: { parentIndices: { select: { projectId: true, parentType: true } } },
    });

    if (!photo || photo.parentIndices.projectId !== projectId || photo.parentIndices.parentType !== parentType) {
      throw new NotFoundException('Photo introuvable');
    }

    await this.prisma.parentIndexPhoto.delete({ where: { id: photoId } });
    return { deleted: true };
  }

  async upsertBabyIndices(
    userId: string,
    projectId: string,
    babyIndex: number,
    dto: UpsertBabyIndicesDto,
  ) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    return this.prisma.babyIndices.upsert({
      where: { projectId_babyIndex: { projectId, babyIndex } },
      create: {
        projectId,
        babyIndex,
        dpa: dto.dpa ? new Date(dto.dpa) : null,
      },
      update: {
        dpa: dto.dpa ? new Date(dto.dpa) : null,
      },
      include: {
        trimesters: {
          include: { photos: { orderBy: { sortOrder: 'asc' } } },
          orderBy: { trimester: 'asc' },
        },
      },
    });
  }

  async upsertBabyTrimester(
    userId: string,
    projectId: string,
    babyIndex: number,
    trimester: Trimester,
    dto: UpsertBabyTrimesterDto,
  ) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    const babyIndices = await this.prisma.babyIndices.upsert({
      where: { projectId_babyIndex: { projectId, babyIndex } },
      create: { projectId, babyIndex },
      update: {},
      select: { id: true },
    });

    return this.prisma.babyTrimesterEntry.upsert({
      where: { babyIndicesId_trimester: { babyIndicesId: babyIndices.id, trimester } },
      create: {
        babyIndicesId: babyIndices.id,
        trimester,
        date: dto.date ? new Date(dto.date) : null,
        note: dto.note ?? null,
        poids: dto.poids ?? null,
        taille: dto.taille ?? null,
        perimCranien: dto.perimCranien ?? null,
      },
      update: {
        date: dto.date ? new Date(dto.date) : null,
        note: dto.note ?? null,
        poids: dto.poids ?? null,
        taille: dto.taille ?? null,
        perimCranien: dto.perimCranien ?? null,
      },
      include: { photos: { orderBy: { sortOrder: 'asc' } } },
    });
  }

  async addTrimesterPhoto(
    userId: string,
    projectId: string,
    babyIndex: number,
    trimester: Trimester,
    url: string,
  ) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    const babyIndices = await this.prisma.babyIndices.upsert({
      where: { projectId_babyIndex: { projectId, babyIndex } },
      create: { projectId, babyIndex },
      update: {},
      select: { id: true },
    });

    const entry = await this.prisma.babyTrimesterEntry.upsert({
      where: { babyIndicesId_trimester: { babyIndicesId: babyIndices.id, trimester } },
      create: { babyIndicesId: babyIndices.id, trimester },
      update: {},
      select: { id: true },
    });

    const count = await this.prisma.babyTrimesterPhoto.count({
      where: { trimesterEntryId: entry.id },
    });

    if (count >= MAX_PHOTOS_PER_SECTION) {
      throw new BadRequestException(`Maximum ${MAX_PHOTOS_PER_SECTION} photos par trimestre`);
    }

    return this.prisma.babyTrimesterPhoto.create({
      data: { trimesterEntryId: entry.id, url, sortOrder: count },
    });
  }

  async deleteTrimesterPhoto(
    userId: string,
    projectId: string,
    babyIndex: number,
    trimester: Trimester,
    photoId: string,
  ) {
    await this.assertAccess(userId, projectId, { adminOnly: true });

    const photo = await this.prisma.babyTrimesterPhoto.findUnique({
      where: { id: photoId },
      include: {
        trimesterEntry: {
          include: {
            babyIndices: { select: { projectId: true, babyIndex: true } },
          },
        },
      },
    });

    if (
      !photo ||
      photo.trimesterEntry.babyIndices.projectId !== projectId ||
      photo.trimesterEntry.babyIndices.babyIndex !== babyIndex ||
      photo.trimesterEntry.trimester !== trimester
    ) {
      throw new NotFoundException('Photo introuvable');
    }

    await this.prisma.babyTrimesterPhoto.delete({ where: { id: photoId } });
    return { deleted: true };
  }
}
