import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import { handlePrismaError } from "src/common/lib/handlePrismaError";
import { ConfigService } from "@nestjs/config";
import {
  formatNoDataResponse,
  formatPaginatedResponse,
  getPaginationOptions,
} from "src/common/lib/pagination-helper";
import { Prisma, CylinderMovement } from "@prisma/client";

@Injectable()
export class CylinderlogsService {
  constructor(
    private prisma: PrismaService,
    private readonly configService: ConfigService
  ) { }

  // FETCH ALL LOGS WITH DURATION
  async findAll(page: number, perPage: number, query?: string, movement?: string, warehouseId?: string, challanDateFrom?: string, challanDateTo?: string, dispatchDateFrom?: string, dispatchDateTo?: string, vehicleId?: string, driverId?: string, user?: any,) {
    try {
      const { skip, take } = getPaginationOptions({ page, perPage });

      const where: any = {};
      if (query) {
        query = query.trim();
        where.OR = [
          { cylinder: { is: { rfid: { contains: query } } } },
          { cylinder: { is: { item_id: { contains: query } } } },
          { cylinder: { is: { client: { is: { name: { contains: query } } } } } },
          { scannedBy: { contains: query } },
        ];
      }
      if (movement) {
        where.AND = [{ movement: movement }];
      }

      if (vehicleId && vehicleId.trim() !== '') {
        where.vehicleId = vehicleId;
      }

      if (driverId && driverId.trim() !== '') {
        where.driverId = driverId;
      }

      if (challanDateFrom || challanDateTo) {
        where.challanDate = {};
        if (challanDateFrom) {
          where.challanDate.gte = new Date(challanDateFrom);
        }
        if (challanDateTo) {
          const toDate = new Date(challanDateTo);
          toDate.setHours(23, 59, 59, 999);
          where.challanDate.lte = toDate;
        }
      }

      if (dispatchDateFrom || dispatchDateTo) {
        where.dispatchDate = {};
        if (dispatchDateFrom) {
          where.dispatchDate.gte = new Date(dispatchDateFrom);
        }
        if (dispatchDateTo) {
          const toDate = new Date(dispatchDateTo);
          toDate.setHours(23, 59, 59, 999);
          where.dispatchDate.lte = toDate;
        }
      }

      if (warehouseId && warehouseId.trim() !== '') {
        where.cylinder = {
          warehouseId: warehouseId,
        };
      } else if (user && user.role !== 'ADMIN') {
        const getUser = await this.prisma.users.findFirst({
          where: {
            id: user.sub
          }
        })
        where.cylinder = {
          warehouseId: getUser?.warehouseId
        };

      }

      const [items, total] = await this.prisma.$transaction([
        this.prisma.cylinder_logs.findMany({
          where,
          include: {
            cylinder: { select: { rfid: true, item_id: true } },
          },
          skip,
          take,
          orderBy: { scannedAt: "desc" },
        }),

        this.prisma.cylinder_logs.count({ where }),
      ]);

      const itemsWithDuration = await Promise.all(
        items.map((log) => this.addDuration(log))
      );

      return formatPaginatedResponse(itemsWithDuration, total, page, perPage);
    } catch (error) {
      handlePrismaError(error, "Fetching Cylinder Logs");
    }
  }


  // GET LOGS FOR SPECIFIC CYLINDER

  async findLogsForCylinder(cylinderId: string) {
    const logs = await this.prisma.cylinder_logs.findMany({
      where: { cylinderId },
      orderBy: { scannedAt: "asc" },
      include: { cylinder: { select: { rfid: true, item_id: true } } },
    });

    if (!logs.length)
      throw new NotFoundException(
        `Logs not found for cylinder ${cylinderId}`
      );

    return Promise.all(logs.map((log) => this.addDuration(log)));
  }

  async findOne(id: string) {
    const log = await this.prisma.cylinder_logs.findUnique({
      where: { id },
      include: { cylinder: { select: { rfid: true, item_id: true } } },
    });

    if (!log) throw new NotFoundException(`Cylinder log not found`);

    return this.addDuration(log);
  }

  // SEARCH CYLINDER LOGS

  async searchCylinderLog(query: string, page: number, perPage: number) {
    const cleanedQuery = query.trim();
    if (!cleanedQuery)
      throw new BadRequestException("Search query is required");

    const { skip, take } = getPaginationOptions({ page, perPage });

    const movementQuery = ["INSIDE", "OUTSIDE", "SCAN"].includes(
      cleanedQuery.toUpperCase()
    )
      ? (cleanedQuery.toUpperCase() as CylinderMovement)
      : undefined;

    const where: Prisma.cylinder_logsWhereInput = {
      OR: [
        movementQuery ? { movement: movementQuery } : null,
        { cylinder: { is: { item_id: { contains: cleanedQuery } } } },
        { cylinder: { is: { rfid: { contains: cleanedQuery } } } },
        { scannedBy: { contains: cleanedQuery } },
      ].filter(Boolean) as Prisma.cylinder_logsWhereInput[],
    };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.cylinder_logs.findMany({
        where,
        include: { cylinder: { select: { rfid: true, item_id: true } } },
        skip,
        take,
        orderBy: { scannedAt: "desc" },
      }),

      this.prisma.cylinder_logs.count({ where }),
    ]);

    if (total === 0) return formatNoDataResponse("Log not found");

    const itemsWithDuration = await Promise.all(
      items.map((log) => this.addDuration(log))
    );

    return formatPaginatedResponse(itemsWithDuration, total, page, perPage);
  }


  // DURATION CALCULATION

  private async addDuration(log: any) {
    let duration = "—";

    if (log.movement === "INSIDE") {
      const lastOutside = await this.prisma.cylinder_logs.findFirst({
        where: {
          cylinderId: log.cylinderId,
          movement: "OUTSIDE",
          scannedAt: { lt: log.scannedAt },
        },
        orderBy: { scannedAt: "desc" },
      });

      if (lastOutside) {
        const diff = new Date(log.scannedAt).getTime() -
          new Date(lastOutside.scannedAt).getTime();

        duration = this.formatDuration(diff);
      }
    }

    return { ...log, duration };
  }

  private formatDuration(ms: number) {
    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
    return days > 0 ? `${days}d` : "0d";
  }
}
