import type { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { ExpenseService } from "../services/ExpenseService.js";
import { ExpensePdf } from "../utils/pdf/ExpensePdf.js";

const createExpenseSchema = z.object({
    amount: z
        .number()
        .min(0, "Amount must be greater than or equal to 0")
        .or(
            z
                .string()
                .regex(/^\d+(\.\d+)?$/)
                .transform(Number)
                .refine((n) => n >= 0, "Amount must be greater than or equal to 0")
        ),
    category: z.string().trim().min(1, "Category cannot be empty").max(100),
    paymentMode: z.enum(["CASH", "UPI", "BANK"]),
    expenseDate: 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 expense date"),
    remarks: z.string().nullable().optional(),
}).strip();

const updateExpenseSchema = createExpenseSchema.partial().strip();

export class ExpenseController {
    constructor(
        private readonly expenseService: ExpenseService = new ExpenseService()
    ) { }

    create = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const validatedData = createExpenseSchema.parse(req.body);
            const expense = await this.expenseService.create({
                ...validatedData,
                createdBy: req.user?.id ? BigInt(req.user.id) : undefined,
            } as any);
            res.status(201).json({ success: true, data: expense });
        } 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;
                });
                res.status(422).json({ message: "Validation failed", errors });
                return;
            }
            const message = error instanceof Error ? error.message : "Failed to create expense";
            res.status(400).json({ message });
        }
    };

    findAll = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { page, limit, search, startDate, endDate } = req.query;
            const pageNum = page !== undefined ? Number(page) : 1;
            const limitNum = limit !== undefined ? Number(limit) : 10;
            const skipNum = (pageNum - 1) * limitNum;
            const takeNum = limitNum;

            const expenses = await this.expenseService.findAll(
                skipNum,
                takeNum,
                search?.toString(),
                startDate?.toString(),
                endDate?.toString()
            );

            const result = {
                data: expenses.items,
                total: expenses.total,
                amountSum: expenses.amountSum,
                meta: {
                    total: expenses.total,
                    page: pageNum,
                    limit: limitNum,
                    totalPages: Math.ceil(expenses.total / limitNum)
                }
            };

            res.status(200).json({ message: "Expenses fetched successfully", result });
        } catch (error) {
            next(error);
        }
    };

    findById = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { id } = req.params;
            const expense = await this.expenseService.findById(Number(id));
            if (!expense) {
                res.status(404).json({ message: "Expense not found" });
                return;
            }
            res.status(200).json({ message: "Expense fetched successfully", result: expense });
        } catch (error) {
            next(error);
        }
    };

    update = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { id } = req.params;
            const validatedData = updateExpenseSchema.parse(req.body);
            const expense = await this.expenseService.update(Number(id), validatedData as any);
            res.status(200).json({ message: "Expense updated successfully", result: expense });
        } 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;
                });
                res.status(422).json({ message: "Validation failed", errors });
                return;
            }
            const message = error instanceof Error ? error.message : "Failed to update expense";
            res.status(400).json({ message });
        }
    };

    delete = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { id } = req.params;
            const expense = await this.expenseService.delete(Number(id));
            res.status(200).json({ message: "Expense deleted successfully", result: expense });
        } catch (error) {
            next(error);
        }
    };

    //PDF Export

    exportPdf = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { search, startDate, endDate } = req.query;

            const expenses = await this.expenseService.findAllForExport(
                search?.toString(),
                startDate?.toString(),
                endDate?.toString()
            );

            if (!expenses || expenses.length === 0) {
                res.status(400).json({ message: "No data available to export" });
                return;
            }

            await ExpensePdf.generate(expenses, res, {
                ...(startDate !== undefined && { startDate: startDate.toString() }),
                ...(endDate !== undefined && { endDate: endDate.toString() }),
                ...(search !== undefined && { search: search.toString() }),
            });
        } catch (error) {
            next(error);
        }
    };

    track = async (req: Request, res: Response, next: NextFunction) => {
        try {
            const { year } = req.query;
            const targetYear = year ? Number(year) : new Date().getFullYear();
            const monthlyExpenses = await this.expenseService.getMonthlyExpenses(targetYear);
            res.status(200).json({ success: true, result: monthlyExpenses });
        } catch (error) {
            next(error);
        }
    };
}