import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../../infra/prisma/prisma.service";
import { CreateServiceOrderDto } from "./dto/create-service-order.dto";
import { ServiceOrderQueryDto } from "./dto/service-order-query.dto";
import { UpdateServiceOrderDto } from "./dto/update-service-order.dto";
import { MaintenanceStatus, EquipmentStatus, FinancialStatus } from "@prisma/client";

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

  async findAll(companyId: string, query: ServiceOrderQueryDto) {
    const { search, status, equipmentId, page = 1, limit = 20 } = query;
    const skip = (page - 1) * limit;

    const where: any = {
      companyId,
      ...(status ? { status } : {}),
      ...(equipmentId ? { equipmentId } : {}),
      ...(search
        ? {
            OR: [
              { code: { contains: search, mode: "insensitive" } },
              { title: { contains: search, mode: "insensitive" } },
              { description: { contains: search, mode: "insensitive" } },
              { equipment: { code: { contains: search, mode: "insensitive" } } },
              { equipment: { model: { contains: search, mode: "insensitive" } } },
              { equipment: { brand: { contains: search, mode: "insensitive" } } },
            ],
          }
        : {}),
    };

    const [items, total] = await Promise.all([
      this.prisma.serviceOrder.findMany({
        where,
        orderBy: { openedAt: "desc" },
        skip,
        take: limit,
        include: {
          equipment: {
            select: {
              id: true,
              code: true,
              assetTag: true,
              brand: true,
              model: true,
              status: true,
              dailyRate: true,
            },
          },
          payables: {
            include: {
              supplier: true,
            },
          },
        },
      }),
      this.prisma.serviceOrder.count({ where }),
    ]);

    const formattedItems = items.map((item) => ({
      ...item,
      cost: Number(item.cost),
      openedAt: item.openedAt.toISOString(),
      closedAt: item.closedAt ? item.closedAt.toISOString() : null,
    }));

    return {
      items: formattedItems,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit) || 1,
    };
  }

  async getStats(companyId: string) {
    const serviceOrders = await this.prisma.serviceOrder.findMany({
      where: { companyId },
    });

    const total = serviceOrders.length;
    const open = serviceOrders.filter((s) => s.status === MaintenanceStatus.OPEN).length;
    const inProgress = serviceOrders.filter((s) => s.status === MaintenanceStatus.IN_PROGRESS).length;
    const done = serviceOrders.filter((s) => s.status === MaintenanceStatus.DONE).length;
    const canceled = serviceOrders.filter((s) => s.status === MaintenanceStatus.CANCELED).length;
    const totalCost = serviceOrders.reduce((acc, s) => acc + Number(s.cost || 0), 0);

    return {
      total,
      open,
      inProgress,
      done,
      canceled,
      totalCost,
    };
  }

  async findOne(companyId: string, id: string) {
    const serviceOrder = await this.prisma.serviceOrder.findFirst({
      where: { id, companyId },
      include: {
        equipment: {
          include: {
            category: true,
          },
        },
        payables: {
          include: {
            supplier: true,
          },
        },
      },
    });

    if (!serviceOrder) {
      throw new NotFoundException("Ordem de Serviço não encontrada.");
    }

    return {
      ...serviceOrder,
      cost: Number(serviceOrder.cost),
      openedAt: serviceOrder.openedAt.toISOString(),
      closedAt: serviceOrder.closedAt ? serviceOrder.closedAt.toISOString() : null,
    };
  }

  async create(companyId: string, dto: CreateServiceOrderDto) {
    const count = await this.prisma.serviceOrder.count({ where: { companyId } });
    const code = `OS-${new Date().getFullYear()}-${String(count + 1).padStart(4, "0")}`;
    const openedDate = dto.openedAt ? new Date(dto.openedAt) : new Date();

    const serviceOrder = await this.prisma.serviceOrder.create({
      data: {
        companyId,
        equipmentId: dto.equipmentId || "",
        code,
        title: dto.title || "Manutenção Preventiva",
        description: dto.description,
        status: dto.status || MaintenanceStatus.OPEN,
        cost: dto.cost ?? 0,
        openedAt: openedDate,
        closedAt: dto.closedAt ? new Date(dto.closedAt) : dto.status === MaintenanceStatus.DONE ? new Date() : null,
      },
      include: {
        equipment: true,
      },
    });

    // Se o usuário solicitou geração no Contas a Pagar (ou se custo > 0 e generatePayable não foi desativado)
    const costNum = Number(dto.cost ?? 0);
    const shouldGeneratePayable = dto.generatePayable !== false && costNum > 0;

    if (shouldGeneratePayable) {
      const dueDate = dto.dueDate
        ? new Date(dto.dueDate)
        : new Date(openedDate.getTime() + 30 * 24 * 60 * 60 * 1000);

      await this.prisma.accountPayable.create({
        data: {
          companyId,
          supplierId: dto.supplierId || null,
          serviceOrderId: serviceOrder.id,
          description: `Manutenção ${code}: ${dto.title || "Ordem de Serviço"}`,
          amount: costNum,
          dueDate,
          status: FinancialStatus.OPEN,
        },
      });
    }

    // Sincronizar status do equipamento se estiver em andamento ou aberta
    if (dto.equipmentId && (dto.status === MaintenanceStatus.IN_PROGRESS || dto.status === MaintenanceStatus.OPEN)) {
      await this.prisma.equipment.update({
        where: { id: dto.equipmentId },
        data: { status: EquipmentStatus.MAINTENANCE },
      });
    }

    return this.findOne(companyId, serviceOrder.id);
  }

  async update(companyId: string, id: string, dto: UpdateServiceOrderDto) {
    const existing = await this.findOne(companyId, id);

    const isFinishing = dto.status === MaintenanceStatus.DONE || dto.status === MaintenanceStatus.CANCELED;
    const closedAt = dto.closedAt
      ? new Date(dto.closedAt)
      : isFinishing && existing.status !== MaintenanceStatus.DONE && existing.status !== MaintenanceStatus.CANCELED
      ? new Date()
      : existing.closedAt
      ? new Date(existing.closedAt)
      : null;

    const updated = await this.prisma.serviceOrder.update({
      where: { id },
      data: {
        ...(dto.equipmentId ? { equipmentId: dto.equipmentId } : {}),
        ...(dto.title ? { title: dto.title } : {}),
        ...(dto.description !== undefined ? { description: dto.description } : {}),
        ...(dto.status ? { status: dto.status } : {}),
        ...(dto.cost !== undefined ? { cost: dto.cost } : {}),
        ...(dto.openedAt ? { openedAt: new Date(dto.openedAt) } : {}),
        closedAt,
      },
      include: {
        equipment: true,
        payables: true,
      },
    });

    // Se houver um título de Contas a Pagar vinculado a esta OS, atualizar o valor se o custo mudou
    if (dto.cost !== undefined && updated.payables.length > 0) {
      await this.prisma.accountPayable.updateMany({
        where: { serviceOrderId: id },
        data: { amount: dto.cost },
      });
    }

    // Se o usuário ativou generatePayable na atualização e ainda não existia título vinculado
    if (dto.generatePayable && updated.payables.length === 0 && Number(updated.cost) > 0) {
      const openedDate = new Date(updated.openedAt);
      const dueDate = dto.dueDate
        ? new Date(dto.dueDate)
        : new Date(openedDate.getTime() + 30 * 24 * 60 * 60 * 1000);

      await this.prisma.accountPayable.create({
        data: {
          companyId,
          supplierId: dto.supplierId || null,
          serviceOrderId: id,
          description: `Manutenção ${updated.code}: ${updated.title}`,
          amount: Number(updated.cost),
          dueDate,
          status: FinancialStatus.OPEN,
        },
      });
    }

    // Atualizar status do equipamento de acordo com o status da OS
    const targetEquipmentId = dto.equipmentId || existing.equipmentId;
    if (dto.status === MaintenanceStatus.IN_PROGRESS || dto.status === MaintenanceStatus.OPEN) {
      await this.prisma.equipment.update({
        where: { id: targetEquipmentId },
        data: { status: EquipmentStatus.MAINTENANCE },
      });
    } else if (isFinishing) {
      await this.prisma.equipment.update({
        where: { id: targetEquipmentId },
        data: { status: EquipmentStatus.AVAILABLE },
      });
    }

    return this.findOne(companyId, id);
  }

  async remove(companyId: string, id: string) {
    const existing = await this.findOne(companyId, id);

    const deleted = await this.prisma.serviceOrder.delete({
      where: { id },
    });

    // Se a máquina estava em manutenção por esta OS, liberar para AVAILABLE
    if (existing.status === MaintenanceStatus.OPEN || existing.status === MaintenanceStatus.IN_PROGRESS) {
      await this.prisma.equipment.update({
        where: { id: existing.equipmentId },
        data: { status: EquipmentStatus.AVAILABLE },
      });
    }

    return deleted;
  }
}
