import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";

export class PurchaseRepository {
  async create(data: Prisma.PurchaseUncheckedCreateInput) {
    return prisma.purchase.create({ data });
  }

  async findAll(
    skip?: number,
    take?: number,
    search?: string,
    dateFrom?: Date,
    dateTo?: Date,
    supplierId?: bigint,
    vegetableId?: bigint,
    status?: string
  ) {
    const where: Prisma.PurchaseWhereInput = { deletedAt: null };

    if (search) {
      where.OR = [
        { lotNo: { contains: search } },
        { billNo: { contains: search } },
        { supplier: { name: { contains: search } } },
        { cashSupplierName: { contains: search } },
        { vegetable: { name: { contains: search } } },
      ];
    }

    if (dateFrom || dateTo) {
      where.purchaseDate = {};
      if (dateFrom) where.purchaseDate.gte = dateFrom;
      if (dateTo) where.purchaseDate.lte = dateTo;
    }

    if (supplierId !== undefined) {
      where.supplierId = supplierId;
    }

    if (vegetableId !== undefined) {
      where.vegetableId = vegetableId;
    }

    if (status) {
      where.status = status as any;
    }

    const findOptions: any = {
      where,
      include: {
        supplier: {
          select: { name: true }
        },
        vegetable: {
          select: { id: true, name: true, isFlexible: true }
        },
      },
      orderBy: { id: 'desc' },
    };
    if (skip !== undefined) findOptions.skip = skip;
    if (take !== undefined) findOptions.take = take;

    const [items, total] = await Promise.all([
      prisma.purchase.findMany(findOptions),
      prisma.purchase.count({ where }),
    ]);

    return { items, total };
  }

  async findById(id: bigint) {
    return prisma.purchase.findFirst({
      where: { id, deletedAt: null },
      include: {
        supplier: {
          select: { name: true }
        },
        vegetable: {
          select: { id: true, name: true, isFlexible: true }
        },
        expenses: {
          where: { category: "Purchase Expense" },
          take: 1
        }
      },
    });
  }

  async update(id: bigint, data: Prisma.PurchaseUncheckedUpdateInput) {
    return prisma.purchase.update({ where: { id }, data });
  }

  async softDelete(id: bigint) {
    return prisma.purchase.update({
      where: { id },
      data: { deletedAt: new Date() },
    });
  }

  async findByLotNo(lotNo: string, excludeId?: bigint) {
    const where: Prisma.PurchaseWhereInput = {
      lotNo,
      deletedAt: null
    };

    if (excludeId) {
      where.id = { not: excludeId };
    }

    // Because lotNo is no longer strictly unique forever, 
    // findFirst is appropriate. We generally only care about the ACTIVE one,
    // which findActiveByLotNo handles. This is kept for generic lookups if needed.
    return prisma.purchase.findFirst({ where });
  }

  async findActiveByLotNo(lotNo: string, tx: Prisma.TransactionClient = prisma) {
    return tx.purchase.findFirst({
      where: {
        lotNo,
        status: { not: "CLOSED" },
        OR: [
          { remainingQuantity: { gt: 0 } },
          { remainingBags: { gt: 0 } },
          { vegetable: { isFlexible: true } },
        ],
        deletedAt: null
      },
      include: { vegetable: true }
    });
  }

  async findAnyByLotNo(lotNo: string, tx: Prisma.TransactionClient = prisma) {
    return tx.purchase.findFirst({
      where: { lotNo, deletedAt: null },
      orderBy: { id: 'desc' },
      select: { vegetableId: true, lotNo: true }
    });
  }

  async findActivePurchasesByLotNo(lotNo: string, tx: Prisma.TransactionClient = prisma) {
    return tx.purchase.findMany({
      where: {
        lotNo,
        status: { not: "CLOSED" },
        OR: [
          { remainingQuantity: { gt: 0 } },
          { remainingBags: { gt: 0 } },
          { vegetable: { isFlexible: true } }
        ],
        deletedAt: null
      },
      include: { vegetable: true },
      orderBy: [
        { purchaseDate: "asc" },
        { createdAt: "asc" },
        { id: "asc" }
      ]
    });
  }

  async autoUpdateStatus(id: bigint, tx: Prisma.TransactionClient = prisma) {
    const purchase = await tx.purchase.findUnique({
      where: { id },
      select: {
        remainingQuantity: true,
        remainingBags: true,
        status: true,
        vegetable: { select: { isFlexible: true } }
      }
    });
    if (!purchase) return;
    if (purchase.status === "CLOSED") return; // Never auto-change CLOSED status
    if (purchase.vegetable?.isFlexible) return; // Flexible lots are closed manually

    const newStatus = purchase.remainingQuantity.gt(0) || purchase.remainingBags.gt(0) ? "ACTIVE" : "DEPLETED";
    if (purchase.status !== newStatus) {
      await tx.purchase.update({
        where: { id },
        data: { status: newStatus }
      });
    }
  }

  async atomicDecrementStock(id: bigint, quantity: number, bags: number = 0, tx: Prisma.TransactionClient = prisma) {
    const result = await tx.purchase.updateMany({
      where: { 
        id, 
        remainingQuantity: { gte: quantity },
        // Also guard against bags if bags > 0
        ...(bags > 0 ? { remainingBags: { gte: bags } } : {})
      },
      data: { 
        remainingQuantity: { decrement: quantity },
        ...(bags > 0 ? { remainingBags: { decrement: bags } } : {})
      },
    });
    
    if (result.count === 0) {
      throw new Error('Stock changed concurrently or insufficient stock, please retry');
    }
  }

  async atomicIncrementStock(id: bigint, quantity: number, bags: number = 0, tx: Prisma.TransactionClient = prisma) {
    return tx.purchase.update({
      where: { id },
      data: {
        remainingQuantity: { increment: quantity },
        ...(bags > 0 ? { remainingBags: { increment: bags } } : {})
      }
    });
  }

  async hasSaleItems(id: bigint): Promise<boolean> {
    // Only count items from active (non-cancelled) sales.
    // Counting items from soft-deleted sales would permanently block
    // editing a purchase lot after any sale against it was cancelled.
    const count = await prisma.saleItem.count({
      where: {
        purchaseId: id,
        sale: { deletedAt: null },
      },
    });
    return count > 0;
  }

  // Dashboard: today's purchase order count (existing)
  async todayPurchaseOrdersCount() {
    const today = new Date();

    const startOfDay = new Date(
      today.getFullYear(),
      today.getMonth(),
      today.getDate()
    );

    const endOfDay = new Date(
      today.getFullYear(),
      today.getMonth(),
      today.getDate() + 1
    );

    const where = {
      createdAt: {
        gte: startOfDay,
        lt: endOfDay,
      },
      deletedAt: null,
    };

    const [items, total] = await Promise.all([
      prisma.purchase.findMany({
        where,
        include: {
          supplier: { select: { name: true } },
          vegetable: { select: { name: true } },
        },
        orderBy: { id: 'desc' },
      }),
      prisma.purchase.count({ where }),
    ]);

    return { items, total };
  }

  // Dashboard: sum of today's purchase grand totals
  async todayPurchaseTotalAmount(): Promise<number> {
    const today = new Date();
    const startOfDay = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0));
    const endOfDay = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999));

    const result = await prisma.purchase.aggregate({
      _sum: { grandTotal: true },
      where: {
        purchaseDate: { gte: startOfDay, lte: endOfDay },
        deletedAt: null,
      },
    });

    return Number(result._sum.grandTotal ?? 0);
  }

  // Dashboard: current stock count for each active vegetable
  async getVegetableStock(): Promise<{ id: number; vegetable: string; remaining: number; remainingBags: number }[]> {
    // Get all active vegetables
    const vegetables = await prisma.vegetable.findMany({
      where: { status: true },
      select: { id: true, name: true },
      orderBy: { name: "asc" },
    });

    if (vegetables.length === 0) return [];

    // Aggregate remaining stock per vegetable
    const groups = await prisma.purchase.groupBy({
      by: ["vegetableId"],
      _sum: { remainingQuantity: true, remainingBags: true },
      where: { deletedAt: null },
    });

    const stockMap = new Map(groups.map((g) => [
      g.vegetableId.toString(), 
      { qty: Number(g._sum.remainingQuantity ?? 0), bags: Number(g._sum.remainingBags ?? 0) }
    ]));

    return vegetables.map((v, idx) => {
      const remaining = stockMap.get(v.id.toString()) || { qty: 0, bags: 0 };
      return {
        id: idx + 1,
        vegetable: v.name,
        remaining: Math.max(0, Math.round(remaining.qty * 100) / 100),
        remainingBags: Math.max(0, Math.round(remaining.bags * 100) / 100),
      };
    });
  }

  //Following functions for Reports modules.

  async getAllPurchases(
    skip?: number,
    take?: number,
    search?: string,
    supplierId?: bigint,
    vegetableId?: bigint,
    dateFrom?: Date,
    dateTo?: Date
  ) {
    const where: Prisma.PurchaseWhereInput = { deletedAt: null };

    if (search) {
      where.OR = [
        { lotNo: { contains: search } },
        { billNo: { contains: search } },
        { supplier: { name: { contains: search } } },
        { vegetable: { name: { contains: search } } },
      ];
    }

    if (supplierId !== undefined) {
      where.supplierId = supplierId;
    }

    if (vegetableId !== undefined) {
      where.vegetableId = vegetableId;
    }

    if (dateFrom || dateTo) {
      where.purchaseDate = {};
      if (dateFrom) where.purchaseDate.gte = dateFrom;
      if (dateTo) where.purchaseDate.lte = dateTo;
    }

    const supplierWhere: Prisma.SupplierWhereInput = { deletedAt: null };
    if (supplierId) {
      supplierWhere.id = supplierId;
    }

    //Sum the values of grand_total, balance_amount and supplier outstanding_balance
    const [totalAgg, outstandingAgg] = await Promise.all([
      prisma.purchase.aggregate({
        _sum: { grandTotal: true, balanceAmount: true },
        where,
      }),
      prisma.supplier.aggregate({
        _sum: { outstandingBalance: true },
        where: supplierWhere,
      }),
    ]);

    const grand_total_sum = totalAgg._sum.grandTotal ?? 0;
    const balance_amount_sum = totalAgg._sum.balanceAmount ?? 0;
    const outstanding_balance_sum = outstandingAgg._sum.outstandingBalance ?? 0;

    const findOptions: any = {
      where,
      include: {
        supplier: {
          select: { name: true, outstandingBalance: true }
        },
        vegetable: {
          select: { name: true }
        },
      },
      orderBy: { id: 'desc' },
    };
    if (skip !== undefined) findOptions.skip = skip;
    if (take !== undefined) findOptions.take = take;

    const [items, total] = await Promise.all([
      prisma.purchase.findMany(findOptions),
      prisma.purchase.count({ where }),
    ]);

    return { items, total, grand_total_sum, balance_amount_sum, outstanding_balance_sum };
  }
}
