import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';

import type { Prisma } from '../../generated/prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateIndicatorDto } from './dto/create-indicator.dto';
import { QueryIndicatorDto } from './dto/query-indicator.dto';
import { UpdateIndicatorDto } from './dto/update-indicator.dto';

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

  async create(dto: CreateIndicatorDto) {
    const name = dto.name.trim();
    const description = dto.description?.trim() || null;

    const duplicate = await this.prisma.indicator.findFirst({
      where: {
        deletedAt: null,
        name: {
          equals: name,
          mode: 'insensitive',
        },
      },
      select: {
        id: true,
      },
    });

    if (duplicate) {
      throw new ConflictException(`Indicator "${name}" already exists`);
    }

    return this.prisma.indicator.create({
      data: {
        name,
        description,
      },
      select: {
        id: true,
        name: true,
        description: true,
        createdAt: true,
        updatedAt: true,
      },
    });
  }

  async findOptions(search?: string) {
    const keyword = search?.trim();

    const where: Prisma.IndicatorWhereInput = {
      deletedAt: null,
      ...(keyword
        ? {
            name: {
              contains: keyword,
              mode: 'insensitive',
            },
          }
        : {}),
    };

    return this.prisma.indicator.findMany({
      where,
      select: {
        id: true,
        name: true,
      },
      orderBy: {
        name: 'asc',
      },
      take: 100,
    });
  }

  async findAll(query: QueryIndicatorDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 10;
    const search = query.search?.trim();
    const skip = (page - 1) * limit;

    const where: Prisma.IndicatorWhereInput = {
      deletedAt: null,
      ...(search
        ? {
            OR: [
              {
                name: {
                  contains: search,
                  mode: 'insensitive',
                },
              },
              {
                description: {
                  contains: search,
                  mode: 'insensitive',
                },
              },
            ],
          }
        : {}),
    };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.indicator.findMany({
        where,
        skip,
        take: limit,
        orderBy: {
          createdAt: 'desc',
        },
        select: {
          id: true,
          name: true,
          description: true,
          createdAt: true,
          updatedAt: true,
          _count: {
            select: {
              rgcDecisions: {
                where: {
                  deletedAt: null,
                },
              },
            },
          },
        },
      }),
      this.prisma.indicator.count({
        where,
      }),
    ]);

    const totalPages = Math.ceil(total / limit);

    return {
      items: items.map((item) => ({
        id: item.id,
        name: item.name,
        description: item.description,
        rgcDecisionCount: item._count.rgcDecisions,
        createdAt: item.createdAt,
        updatedAt: item.updatedAt,
      })),
      meta: {
        page,
        limit,
        total,
        totalPages,
        hasNextPage: page < totalPages,
        hasPreviousPage: page > 1,
      },
    };
  }

  async findOne(id: number) {
    const indicator = await this.prisma.indicator.findFirst({
      where: {
        id,
        deletedAt: null,
      },
      select: {
        id: true,
        name: true,
        description: true,
        createdAt: true,
        updatedAt: true,
        _count: {
          select: {
            rgcDecisions: {
              where: {
                deletedAt: null,
              },
            },
          },
        },
      },
    });

    if (!indicator) {
      throw new NotFoundException(`Indicator with ID ${id} was not found`);
    }

    return {
      id: indicator.id,
      name: indicator.name,
      description: indicator.description,
      rgcDecisionCount: indicator._count.rgcDecisions,
      createdAt: indicator.createdAt,
      updatedAt: indicator.updatedAt,
    };
  }

  async update(id: number, dto: UpdateIndicatorDto) {
    const existing = await this.prisma.indicator.findFirst({
      where: {
        id,
        deletedAt: null,
      },
      select: {
        id: true,
        name: true,
        description: true,
      },
    });

    if (!existing) {
      throw new NotFoundException(`Indicator with ID ${id} was not found`);
    }

    const name = dto.name !== undefined ? dto.name.trim() : existing.name;

    const description =
      dto.description !== undefined
        ? dto.description.trim() || null
        : existing.description;

    const duplicate = await this.prisma.indicator.findFirst({
      where: {
        id: {
          not: id,
        },
        deletedAt: null,
        name: {
          equals: name,
          mode: 'insensitive',
        },
      },
      select: {
        id: true,
      },
    });

    if (duplicate) {
      throw new ConflictException(`Indicator "${name}" already exists`);
    }

    return this.prisma.indicator.update({
      where: {
        id,
      },
      data: {
        name,
        description,
      },
      select: {
        id: true,
        name: true,
        description: true,
        createdAt: true,
        updatedAt: true,
      },
    });
  }

  async remove(id: number) {
    const indicator = await this.prisma.indicator.findFirst({
      where: {
        id,
        deletedAt: null,
      },
      select: {
        id: true,
      },
    });

    if (!indicator) {
      throw new NotFoundException(`Indicator with ID ${id} was not found`);
    }

    return this.prisma.indicator.update({
      where: {
        id,
      },
      data: {
        deletedAt: new Date(),
      },
      select: {
        id: true,
        name: true,
        description: true,
        deletedAt: true,
      },
    });
  }

  async restore(id: number) {
    const indicator = await this.prisma.indicator.findUnique({
      where: {
        id,
      },
      select: {
        id: true,
        name: true,
        description: true,
        deletedAt: true,
      },
    });

    if (!indicator) {
      throw new NotFoundException(`Indicator with ID ${id} was not found`);
    }

    if (indicator.deletedAt === null) {
      throw new ConflictException(
        `Indicator "${indicator.name}" is already active`,
      );
    }

    const duplicate = await this.prisma.indicator.findFirst({
      where: {
        id: {
          not: id,
        },
        deletedAt: null,
        name: {
          equals: indicator.name,
          mode: 'insensitive',
        },
      },
      select: {
        id: true,
      },
    });

    if (duplicate) {
      throw new ConflictException(
        `Cannot restore indicator because "${indicator.name}" already exists`,
      );
    }

    return this.prisma.indicator.update({
      where: {
        id,
      },
      data: {
        deletedAt: null,
      },
      select: {
        id: true,
        name: true,
        description: true,
        createdAt: true,
        updatedAt: true,
        deletedAt: true,
      },
    });
  }
}
