import { QuotationRepository } from "../repositories/QuotationRepository.js";
import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";
import { QuotationPdf } from "../utils/pdf/QuotationPdf.js";
import type { Response } from "express";
import { ShopRepository } from "../repositories/ShopRepository.js";

export class QuotationService {
  constructor(
    private readonly quotationRepository = new QuotationRepository(),
    private readonly shopRepository = new ShopRepository()
  ) {}

  // Generates a candidate quotation number based on max existing ID.
  // Using count() breaks after deletions (e.g. count=4 after deleting QTN-00003
  // would collide with the existing QTN-00005). Max-ID is monotonically safe.
  private generateQuotationNoCandidate(maxId: number): string {
    const nextId = maxId + 1;
    return `QTN-${String(nextId).padStart(5, "0")}`;
  }

  async createQuotation(data: any) {
    const { items, ...quotationFields } = data;

    // Resolve max existing quotation ID and generate a unique quotation number.
    // Using count() would collide after deletions; max-ID is monotonically safe.
    let quotationNo: string = "";
    let isUnique = false;
    const maxRecord = await this.quotationRepository.findMaxId();
    let maxId = maxRecord ?? 0;
    while (!isUnique) {
      quotationNo = this.generateQuotationNoCandidate(maxId);
      const conflict = await this.quotationRepository.findByQuotationNo(quotationNo);
      if (!conflict) isUnique = true;
      else maxId++; // increment to skip over the conflicting entry
    }
    quotationFields.quotationNo = quotationNo;

    // Calculate totals to ensure accuracy
    let calculatedAmount = new Prisma.Decimal(0);
    const validatedItems: any[] = [];

    for (const item of items) {
      if (!item.quantity || Number(item.quantity) <= 0) {
        throw new Error("Item quantity must be greater than 0.");
      }

      const qty = new Prisma.Decimal(item.quantity);
      const price = new Prisma.Decimal(item.price);
      const amount = qty.mul(price).toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

      calculatedAmount = calculatedAmount.add(amount);

      validatedItems.push({
        vegetableId: item.vegetableId ? BigInt(item.vegetableId) : null,
        vegName: item.vegName,
        hsnCode: item.hsnCode || null,
        quantity: qty,
        unit: item.unit || "Kg",
        price: price,
        amount: amount,
      });
    }

    const extraCharge = new Prisma.Decimal(quotationFields.extraCharge || 0).toDecimalPlaces(2);
    const discount = new Prisma.Decimal(quotationFields.discount || 0).toDecimalPlaces(2);
    const totalAmount = calculatedAmount
      .add(extraCharge)
      .sub(discount)
      .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

    const finalData = {
      ...quotationFields,
      customerId: quotationFields.customerId ? BigInt(quotationFields.customerId) : null,
      quotationDate: new Date(quotationFields.quotationDate),
      amount: calculatedAmount,
      extraCharge: extraCharge,
      discount: discount,
      totalAmount: totalAmount,
      createdBy: quotationFields.createdBy ? BigInt(quotationFields.createdBy) : null,
      items: validatedItems.map((item) => ({
        ...item,
        createdBy: quotationFields.createdBy ? BigInt(quotationFields.createdBy) : null,
      })),
    };

    return this.quotationRepository.create(finalData);
  }

  async updateQuotation(id: bigint, data: any) {
    const existing = await this.quotationRepository.findById(id);
    if (!existing) throw new Error("Quotation not found");

    const { items, ...quotationFields } = data;

    let calculatedAmount = new Prisma.Decimal(0);
    const validatedItems: any[] = [];

    for (const item of items) {
      if (!item.quantity || Number(item.quantity) <= 0) {
        throw new Error("Item quantity must be greater than 0.");
      }

      const qty = new Prisma.Decimal(item.quantity);
      const price = new Prisma.Decimal(item.price);
      const amount = qty.mul(price).toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

      calculatedAmount = calculatedAmount.add(amount);

      validatedItems.push({
        vegetableId: item.vegetableId ? BigInt(item.vegetableId) : null,
        vegName: item.vegName,
        hsnCode: item.hsnCode || null,
        quantity: qty,
        unit: item.unit || "Kg",
        price: price,
        amount: amount,
      });
    }

    const extraCharge = new Prisma.Decimal(quotationFields.extraCharge || 0).toDecimalPlaces(2);
    const discount = new Prisma.Decimal(quotationFields.discount || 0).toDecimalPlaces(2);
    const totalAmount = calculatedAmount
      .add(extraCharge)
      .sub(discount)
      .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

    const finalData = {
      ...quotationFields,
      customerId: quotationFields.customerId ? BigInt(quotationFields.customerId) : null,
      quotationDate: new Date(quotationFields.quotationDate),
      amount: calculatedAmount,
      extraCharge: extraCharge,
      discount: discount,
      totalAmount: totalAmount,
      items: validatedItems,
    };

    return this.quotationRepository.update(id, finalData);
  }

  async getQuotations(
    page: number = 1,
    limit: number = 10,
    search?: string,
    dateFromStr?: string,
    dateToStr?: string
  ) {
    const skip = (page - 1) * limit;
    let dateFrom: Date | undefined;
    let dateTo: Date | undefined;
    if (dateFromStr) dateFrom = new Date(dateFromStr);
    if (dateToStr) dateTo = new Date(dateToStr);

    const { items, total } = await this.quotationRepository.findAll(
      skip,
      limit,
      search,
      dateFrom,
      dateTo
    );

    return {
      data: items,
      meta: { total, page, limit, totalPages: Math.ceil(total / limit) },
    };
  }

  async getQuotationById(id: bigint) {
    const quotation = await this.quotationRepository.findById(id);
    if (!quotation) throw new Error("Quotation not found");
    return quotation;
  }

  async deleteQuotation(id: bigint) {
    const quotation = await this.quotationRepository.findById(id);
    if (!quotation) throw new Error("Quotation not found");
    return this.quotationRepository.delete(id);
  }

  async generatePdf(id: bigint, res: Response): Promise<void> {
    const quotation = await this.getQuotationById(id);
    if (!quotation) throw new Error("Quotation not found");

    const shop = await this.shopRepository.findFirst();

    QuotationPdf.generate(
      {
        quotation,
        shop,
      },
      res
    );
  }
}
