import type { Request, Response } from "express";
import { z } from "zod";
import { PurchaseService } from "../services/PurchaseService.js";
import Unit from "../constants/units.js";

const createPurchaseSchema = z.object({
  purchaseDate: z
    .string()
    .datetime()
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .transform((str) => new Date(str))
    .refine((date) => {
      const today = new Date();
      today.setHours(23, 59, 59, 999);
      return date <= today;
    }, "Future date is not allowed for purchase date"),
  billNo: z.string().max(50).optional(),
  supplierId: z.number().int().positive().or(z.string().regex(/^\d+$/).transform(Number)).optional().nullable(),
  cashSupplierName: z.string().max(255).optional().nullable(),
  vegetableId: z.preprocess(
    (val) => (val === null || val === undefined || val === '' || val === 'undefined' ? undefined : Number(val)),
    z.number({ error: () => "Vegetable is required" }).int().positive("Vegetable is required")
  ),
  lotNo: z.string().trim().min(1, "Lot number cannot be empty").max(100),
  unit: z.enum(["KG", "BAG", "BOX", "THAAR", "PIECES"]),
  rateType: z.enum(["UNIT", "KG"]).optional(),
  bags: z.coerce.number({ message: "Number of bags must be a valid number" }).int("Number of bags must be a whole number").positive("Number of bags must be greater than 0").optional(),
  quantity: z.number().min(0, "Quantity must be non-negative").or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Quantity must be non-negative")).optional(),
  ratePerUnit: z.preprocess(
    (val) => (val === null || val === undefined || val === '' ? undefined : Number(val)),
    z.number({ error: () => "Rate per unit is required" })
      .min(1, "Rate per unit must be at least 1")
  ).refine((v) => v !== undefined, { message: "Rate per unit is required" }),
  transportFee: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Fee must be non-negative").optional().nullable()),
  loadingUnloadingFee: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Fee must be non-negative").optional().nullable()),
  otherCharges: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Fee must be non-negative").optional().nullable()),
  thaiyalCharges: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Fee must be non-negative").optional().nullable()),
  extraCharges: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Fee must be non-negative").optional().nullable()),
  arrivalDate: z
    .string()
    .datetime()
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .transform((str) => new Date(str))
    .refine((date) => {
      const today = new Date();
      today.setHours(23, 59, 59, 999);
      return date <= today;
    }, "Future date is not allowed for arrival date")
    .optional(),
  advanceAmount: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Advance must be non-negative").optional().nullable()),
  expenseAmount: z.number().min(0).optional().nullable().or(z.string().regex(/^\d+(\.\d+)?$/).transform(Number).refine(n => n >= 0, "Expense must be non-negative").optional().nullable()),
  expensePaymentMode: z.enum(["CASH", "BANK", "UPI"]).optional().nullable(),
  remarks: z.string().optional()
}).strip().superRefine((data, ctx) => {

  // Validate Purchase Date vs Arrival Date
  if (
    data.arrivalDate &&
    data.purchaseDate &&
    data.arrivalDate < data.purchaseDate
  ) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ["arrivalDate"],
      message: "Arrival date cannot be earlier than purchase date",
    });
  }

  if (data.unit === "KG") {
    if (data.quantity === undefined) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["quantity"],
        message: "Quantity is required when unit is KG",
      });
    }
    return;
  }

  if (data.bags === undefined) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ["bags"],
      message:
        data.unit === "THAAR"
          ? "Thaar count is required when unit is THAAR"
          : data.unit === "PIECES"
            ? "Piece count is required when unit is PIECES"
            : "Bags is required when unit is BOX/BAG",
    });
  }

  if (data.rateType === "KG" && (data.quantity === undefined || data.quantity <= 0)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ["quantity"],
      message: "Quantity is required when rate type is Rate / Kg",
    });
  }

}); // Use strip to remove any derived fields like goodsTotal or paymentStatus

const updatePurchaseSchema = createPurchaseSchema;

const bulkCreatePurchaseSchema = z.object({
  purchases: z
    .array(createPurchaseSchema)
    .min(1, "At least one purchase record is required"),
});

export class PurchaseController {
  constructor(private readonly purchaseService = new PurchaseService()) { }

  create = async (req: Request, res: Response): Promise<void> => {
    try {
      const validatedData = createPurchaseSchema.parse(req.body);
      const result = await this.purchaseService.createPurchase({
        ...validatedData,
        supplierId: validatedData.supplierId ? BigInt(validatedData.supplierId) : null,
        cashSupplierName: validatedData.cashSupplierName ?? null,
        vegetableId: BigInt(validatedData.vegetableId),
        createdBy: BigInt(req.user!.id),
      });
      res.status(201).json({ message: "Purchase created successfully", result });
    } catch (error: any) {
      if (error instanceof z.ZodError) {
        const errors: Record<string, string> = {};
        error.issues.forEach((err) => {
          if (err.path[0]) {
            errors[err.path[0].toString()] = err.message;
          }
        });
        const firstError = error.issues[0]?.message || "Validation failed";
        res.status(422).json({ message: firstError, errors });
        return;
      }
      const message = error instanceof Error ? error.message : "Failed to create purchase";
      res.status(400).json({ message });
    }
  };

  bulkCreate = async (req: Request, res: Response): Promise<void> => {
    try {
      const { purchases: rawPurchases } = bulkCreatePurchaseSchema.parse(req.body);

      // Attach createdBy to every record
      const purchases = rawPurchases.map((p) => ({
        ...p,
        supplierId: p.supplierId ? BigInt(p.supplierId) : null,
        cashSupplierName: p.cashSupplierName ?? null,
        vegetableId: BigInt(p.vegetableId),
        createdBy: BigInt(req.user!.id),
      }));

      const { created, errors } = await this.purchaseService.bulkCreatePurchases(purchases);

      const hasCreated = created.length > 0;
      const hasErrors = errors.length > 0;

      // 207 Multi-Status: some created, some failed
      // 201 Created: all succeeded
      // 422 Unprocessable Entity: all failed
      const statusCode = hasCreated && hasErrors ? 207 : hasCreated ? 201 : 422;

      res.status(statusCode).json({
        message: hasCreated
          ? `${created.length} purchase${created.length > 1 ? "s" : ""} created successfully${hasErrors ? `, ${errors.length} failed` : ""}`
          : "All purchases failed validation",
        result: { created, errors },
      });
    } catch (error: any) {
      if (error instanceof z.ZodError) {
        const errors: Record<string, string> = {};
        error.issues.forEach((err) => {
          const field = err.path.join(".");
          if (field) errors[field] = err.message;
        });
        res.status(422).json({ message: "Validation failed", errors });
        return;
      }
      const message = error instanceof Error ? error.message : "Failed to bulk create purchases";
      res.status(400).json({ message });
    }
  };

  getAll = async (req: Request, res: Response): Promise<void> => {
    try {
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;
      const search = req.query.search as string;
      const dateFrom = req.query.dateFrom as string;
      const dateTo = req.query.dateTo as string;
      const supplierId = req.query.supplierId as string;
      const status = req.query.status as string;

      const result = await this.purchaseService.getPurchases(
        page, limit, search, dateFrom, dateTo, supplierId, status
      );
      res.status(200).json({ message: "Purchases fetched successfully", result });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to fetch purchases";
      res.status(500).json({ message });
    }
  };

  getById = async (req: Request, res: Response): Promise<void> => {
    try {
      const id = BigInt(req.params.id as string | number);
      const result = await this.purchaseService.getPurchaseById(id);
      res.status(200).json(result);
    } catch (error) {
      const message = error instanceof Error ? error.message : "Purchase not found";
      res.status(404).json({ message });
    }
  };

  update = async (req: Request, res: Response): Promise<void> => {
    try {
      const id = BigInt(req.params.id as string | number);
      const validatedData = updatePurchaseSchema.parse(req.body);

      const updateData: any = { ...validatedData };
      if (validatedData.supplierId) updateData.supplierId = BigInt(validatedData.supplierId);
      if (validatedData.vegetableId) updateData.vegetableId = BigInt(validatedData.vegetableId);

      const result = await this.purchaseService.updatePurchase(id, updateData);
      res.status(200).json({ message: "Purchase updated successfully", result });
    } catch (error: any) {
      if (error instanceof z.ZodError) {
        const errors: Record<string, string> = {};
        error.issues.forEach((err) => {
          if (err.path[0]) {
            errors[err.path[0].toString()] = err.message;
          }
        });
        const firstError = error.issues[0]?.message || "Validation failed";
        res.status(422).json({ message: firstError, errors });
        return;
      }
      const message = error instanceof Error ? error.message : "Failed to update purchase";
      res.status(400).json({ message });
    }
  };

  delete = async (req: Request, res: Response): Promise<void> => {
    try {
      const id = BigInt(req.params.id as string | number);
      await this.purchaseService.deletePurchase(id);
      res.status(200).json({ message: "Purchase deleted successfully" });
    } catch (error: any) {
      const message = error instanceof Error ? error.message : "Failed to delete purchase";
      res.status(400).json({ message });
    }
  };

  close = async (req: Request, res: Response): Promise<void> => {
    try {
      const id = BigInt(req.params.id as string | number);
      const result = await this.purchaseService.closePurchase(id);
      res.status(200).json({ message: "Purchase closed successfully", result });
    } catch (error: any) {
      const message = error instanceof Error ? error.message : "Failed to close purchase";
      res.status(400).json({ message });
    }
  };

  getMeta = async (_req: Request, res: Response): Promise<void> => {
    try {
      const purchaseUnits = Object.entries(Unit).map(([value, label]) => ({ value, label }));
      const { suppliers, vegetables } = await this.purchaseService.getPurchaseMeta();
      res.status(200).json({
        message: "Purchase meta fetched successfully",
        result: { purchaseUnits, suppliers, vegetables },
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to fetch purchase meta";
      res.status(500).json({ message });
    }
  };

  checkLot = async (req: Request, res: Response): Promise<void> => {
    try {
      const lotNo = req.query.lotNo as string | undefined;
      const vegetableId = req.query.vegetableId as string | undefined;

      if (!lotNo || !lotNo.trim()) {
        res.status(400).json({ success: false, message: "lotNo is required." });
        return;
      }

      const result = await this.purchaseService.checkLotAvailability(lotNo.trim(), vegetableId);

      res.status(200).json({
        success: true,
        data: {
          lotNo: lotNo.trim(),
          isAvailable: result.isAvailable,
          message: result.message,
        },
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to check lot availability.";
      res.status(500).json({ success: false, message });
    }
  };
}
