/**
 * CRUD Route Factory
 * Generates standardized CRUD routes with consistent patterns
 */

const { query } = require("../config/database");
const { asyncHandler } = require("../middleware/errorHandler");
const { requireAuth } = require("../middleware/auth");
const { sendSuccess, sendNotFound } = require("../utils/responseHelpers");
const { getById, deleteById, countRecords } = require("./queryHelpers");
const { validateRequiredFields, generateSlug } = require("./validation");
const { HTTP_STATUS } = require("../config/constants");

/**
 * Create standardized CRUD routes for a resource
 * @param {Object} config - Configuration object
 * @param {string} config.table - Database table name
 * @param {string} config.resourceName - Resource name (plural, e.g., 'products')
 * @param {string} config.singularName - Singular resource name (e.g., 'product')
 * @param {string[]} config.listFields - Fields to select in list endpoint
 * @param {string[]} config.requiredFields - Required fields for creation
 * @param {Function} config.beforeCreate - Hook before creation
 * @param {Function} config.afterCreate - Hook after creation
 * @param {Function} config.beforeUpdate - Hook before update
 * @param {Function} config.afterUpdate - Hook after update
 * @param {Function} config.cacheInvalidate - Function to invalidate cache
 * @returns {Object} Object with route handlers
 */
const createCRUDHandlers = (config) => {
  const {
    table,
    resourceName,
    singularName,
    listFields = "*",
    requiredFields = [],
    beforeCreate,
    afterCreate,
    beforeUpdate,
    afterUpdate,
    cacheInvalidate,
  } = config;

  return {
    /**
     * List all resources
     * GET /:resource
     */
    list: asyncHandler(async (req, res) => {
      const result = await query(
        `SELECT ${listFields} FROM ${table} ORDER BY createdat DESC`
      );
      sendSuccess(res, { [resourceName]: result.rows });
    }),

    /**
     * Get single resource by ID
     * GET /:resource/:id
     */
    getById: asyncHandler(async (req, res) => {
      const item = await getById(table, req.params.id);
      if (!item) {
        return sendNotFound(res, singularName);
      }
      sendSuccess(res, { [singularName]: item });
    }),

    /**
     * Create new resource
     * POST /:resource
     */
    create: asyncHandler(async (req, res) => {
      // Validate required fields
      if (requiredFields.length > 0) {
        validateRequiredFields(req.body, requiredFields);
      }

      // Run beforeCreate hook if provided
      let data = { ...req.body };
      if (beforeCreate) {
        data = await beforeCreate(data, req);
      }

      // Build insert query dynamically
      const fields = Object.keys(data);
      const placeholders = fields.map((_, i) => `$${i + 1}`).join(", ");
      const values = fields.map((key) => data[key]);

      const result = await query(
        `INSERT INTO ${table} (${fields.join(", ")}, createdat) 
         VALUES (${placeholders}, NOW()) 
         RETURNING *`,
        values
      );

      let created = result.rows[0];

      // Run afterCreate hook if provided
      if (afterCreate) {
        created = await afterCreate(created, req);
      }

      // Invalidate cache if function provided
      if (cacheInvalidate) {
        cacheInvalidate();
      }

      sendSuccess(
        res,
        {
          [singularName]: created,
          message: `${singularName} created successfully`,
        },
        HTTP_STATUS.CREATED
      );
    }),

    /**
     * Update resource by ID
     * PUT /:resource/:id
     */
    update: asyncHandler(async (req, res) => {
      // Check if resource exists
      const existing = await getById(table, req.params.id);
      if (!existing) {
        return sendNotFound(res, singularName);
      }

      // Run beforeUpdate hook if provided
      let data = { ...req.body };
      if (beforeUpdate) {
        data = await beforeUpdate(data, req, existing);
      }

      // Build update query dynamically
      const updates = [];
      const values = [];
      let paramIndex = 1;

      Object.entries(data).forEach(([key, value]) => {
        if (value !== undefined) {
          updates.push(`${key} = $${paramIndex}`);
          values.push(value);
          paramIndex++;
        }
      });

      if (updates.length === 0) {
        return sendSuccess(res, {
          [singularName]: existing,
          message: "No changes to update",
        });
      }

      updates.push(`updatedat = NOW()`);
      values.push(req.params.id);

      const result = await query(
        `UPDATE ${table} SET ${updates.join(
          ", "
        )} WHERE id = $${paramIndex} RETURNING *`,
        values
      );

      let updated = result.rows[0];

      // Run afterUpdate hook if provided
      if (afterUpdate) {
        updated = await afterUpdate(updated, req, existing);
      }

      // Invalidate cache if function provided
      if (cacheInvalidate) {
        cacheInvalidate();
      }

      sendSuccess(res, {
        [singularName]: updated,
        message: `${singularName} updated successfully`,
      });
    }),

    /**
     * Delete resource by ID
     * DELETE /:resource/:id
     */
    delete: asyncHandler(async (req, res) => {
      const deleted = await deleteById(table, req.params.id);
      if (!deleted) {
        return sendNotFound(res, singularName);
      }

      // Invalidate cache if function provided
      if (cacheInvalidate) {
        cacheInvalidate();
      }

      sendSuccess(res, {
        message: `${singularName} deleted successfully`,
      });
    }),
  };
};

/**
 * Attach CRUD handlers to a router
 * @param {Router} router - Express router
 * @param {string} path - Base path for routes
 * @param {Object} handlers - CRUD handlers object
 * @param {Function} authMiddleware - Authentication middleware (default: requireAuth)
 */
const attachCRUDRoutes = (
  router,
  path,
  handlers,
  authMiddleware = requireAuth
) => {
  router.get(`/${path}`, authMiddleware, handlers.list);
  router.get(`/${path}/:id`, authMiddleware, handlers.getById);
  router.post(`/${path}`, authMiddleware, handlers.create);
  router.put(`/${path}/:id`, authMiddleware, handlers.update);
  router.delete(`/${path}/:id`, authMiddleware, handlers.delete);
};

module.exports = {
  createCRUDHandlers,
  attachCRUDRoutes,
};
