// src/routes/budget.ts
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import passport from '../config/passport';
import { budgetRepo } from '../services/db/index.db';
import type { UserProfile } from '../types';
import { validateRequest } from '../middleware/validation.middleware';
import { requiredString, numericIdParam } from '../utils/zodUtils';
import { budgetUpdateLimiter } from '../config/rateLimiters';
import { sendSuccess, sendNoContent } from '../utils/apiResponse';

const router = express.Router();

// --- Zod Schemas for Budget Routes (as per ADR-003) ---
const budgetIdParamSchema = numericIdParam(
  'id',
  "Invalid ID for parameter 'id'. Must be a number.",
);

const createBudgetSchema = z.object({
  body: z.object({
    name: requiredString('Budget name is required.'),
    amount_cents: z.number().int().positive('Amount must be a positive integer.'),
    period: z.enum(['weekly', 'monthly']),
    start_date: z.string().date('Start date must be a valid date in YYYY-MM-DD format.'),
  }),
});

const updateBudgetSchema = budgetIdParamSchema.extend({
  body: createBudgetSchema.shape.body.partial().refine((data) => Object.keys(data).length > 0, {
    message: 'At least one field to update must be provided.',
  }),
});

const spendingAnalysisSchema = z.object({
  query: z.object({
    startDate: z.string().date('startDate must be a valid date in YYYY-MM-DD format.'),
    endDate: z.string().date('endDate must be a valid date in YYYY-MM-DD format.'),
  }),
});

// Middleware to ensure user is authenticated for all budget routes
router.use(passport.authenticate('jwt', { session: false }));

// Apply rate limiting to all subsequent budget routes
router.use(budgetUpdateLimiter);

/**
 * @openapi
 * /budgets:
 *   get:
 *     tags: [Budgets]
 *     summary: Get all budgets
 *     description: Retrieve all budgets for the authenticated user.
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: List of user budgets
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
  const userProfile = req.user as UserProfile;
  try {
    const budgets = await budgetRepo.getBudgetsForUser(userProfile.user.user_id, req.log);
    sendSuccess(res, budgets);
  } catch (error) {
    req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching budgets');
    next(error);
  }
});

/**
 * @openapi
 * /budgets:
 *   post:
 *     tags: [Budgets]
 *     summary: Create budget
 *     description: Create a new budget for the authenticated user.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - name
 *               - amount_cents
 *               - period
 *               - start_date
 *             properties:
 *               name:
 *                 type: string
 *                 description: Budget name
 *               amount_cents:
 *                 type: integer
 *                 minimum: 1
 *                 description: Budget amount in cents
 *               period:
 *                 type: string
 *                 enum: [weekly, monthly]
 *                 description: Budget period
 *               start_date:
 *                 type: string
 *                 format: date
 *                 description: Budget start date (YYYY-MM-DD)
 *     responses:
 *       201:
 *         description: Budget created
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.post(
  '/',
  validateRequest(createBudgetSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type CreateBudgetRequest = z.infer<typeof createBudgetSchema>;
    const { body } = req as unknown as CreateBudgetRequest;
    try {
      const newBudget = await budgetRepo.createBudget(userProfile.user.user_id, body, req.log);
      sendSuccess(res, newBudget, 201);
    } catch (error: unknown) {
      req.log.error({ error, userId: userProfile.user.user_id, body }, 'Error creating budget');
      next(error);
    }
  },
);

/**
 * @openapi
 * /budgets/{id}:
 *   put:
 *     tags: [Budgets]
 *     summary: Update budget
 *     description: Update an existing budget.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: Budget ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name:
 *                 type: string
 *                 description: Budget name
 *               amount_cents:
 *                 type: integer
 *                 minimum: 1
 *                 description: Budget amount in cents
 *               period:
 *                 type: string
 *                 enum: [weekly, monthly]
 *                 description: Budget period
 *               start_date:
 *                 type: string
 *                 format: date
 *                 description: Budget start date (YYYY-MM-DD)
 *     responses:
 *       200:
 *         description: Budget updated
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Validation error - at least one field required
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       404:
 *         description: Budget not found
 */
router.put(
  '/:id',
  validateRequest(updateBudgetSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type UpdateBudgetRequest = z.infer<typeof updateBudgetSchema>;
    const { params, body } = req as unknown as UpdateBudgetRequest;
    try {
      const updatedBudget = await budgetRepo.updateBudget(
        params.id,
        userProfile.user.user_id,
        body,
        req.log,
      );
      sendSuccess(res, updatedBudget);
    } catch (error: unknown) {
      req.log.error(
        { error, userId: userProfile.user.user_id, budgetId: params.id },
        'Error updating budget',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /budgets/{id}:
 *   delete:
 *     tags: [Budgets]
 *     summary: Delete budget
 *     description: Delete a budget by ID.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: Budget ID
 *     responses:
 *       204:
 *         description: Budget deleted
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       404:
 *         description: Budget not found
 */
router.delete(
  '/:id',
  validateRequest(budgetIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type DeleteBudgetRequest = z.infer<typeof budgetIdParamSchema>;
    const { params } = req as unknown as DeleteBudgetRequest;
    try {
      await budgetRepo.deleteBudget(params.id, userProfile.user.user_id, req.log);
      sendNoContent(res);
    } catch (error: unknown) {
      req.log.error(
        { error, userId: userProfile.user.user_id, budgetId: params.id },
        'Error deleting budget',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /budgets/spending-analysis:
 *   get:
 *     tags: [Budgets]
 *     summary: Get spending analysis
 *     description: Get spending breakdown by category for a date range.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: startDate
 *         required: true
 *         schema:
 *           type: string
 *           format: date
 *         description: Start date (YYYY-MM-DD)
 *       - in: query
 *         name: endDate
 *         required: true
 *         schema:
 *           type: string
 *           format: date
 *         description: End date (YYYY-MM-DD)
 *     responses:
 *       200:
 *         description: Spending breakdown by category
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Invalid date format
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.get(
  '/spending-analysis',
  validateRequest(spendingAnalysisSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type SpendingAnalysisRequest = z.infer<typeof spendingAnalysisSchema>;
    const {
      query: { startDate, endDate },
    } = req as unknown as SpendingAnalysisRequest;

    try {
      const spendingData = await budgetRepo.getSpendingByCategory(
        userProfile.user.user_id,
        startDate,
        endDate,
        req.log,
      );
      sendSuccess(res, spendingData);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, startDate, endDate },
        'Error fetching spending analysis',
      );
      next(error);
    }
  },
);

export default router;
