import { HttpError } from "@batch-cooking/error-tools";
import { wrapAsyncHandler } from "@batch-cooking/express-tools";
import {
  createRecipeSchema,
  ErrorCode,
  listRecipesSchema,
  submitTechStepCorrectionSchema,
  updateRecipeSchema,
} from "@batch-cooking/shared";
import { Router } from "express";
import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js";
import {
  addFavorite,
  createRecipe,
  deleteRecipe,
  getRecipe,
  listRecipes,
  removeFavorite,
  updateRecipe,
} from "./recipe.service.js";
import {
  listTechStepCorrections,
  submitTechStepCorrection,
} from "./recipe-tech-step-correction.service.js";

/** Router mounted at `/recipes` in app.ts. Every route requires a session — the catalog is shared across households, not public (same reasoning as `planning`/`house`: it's app content, not signup-time reference data). */
export const recipeRouter = Router();

/** Parses and validates an `:id` route param, shared by every route below that targets one recipe. */
function parseRecipeId(rawId: string | undefined): number {
  const id = Number(rawId);
  if (!Number.isInteger(id)) {
    throw new HttpError(400, ErrorCode.VALIDATION_ERROR, "id must be an integer");
  }
  return id;
}

/** Same shape as {@link parseRecipeId}, for the `:stepId` route param of the tech-step-correction routes below — a distinct function only so the error message names the right param. */
function parseStepId(rawId: string | undefined): number {
  const id = Number(rawId);
  if (!Number.isInteger(id)) {
    throw new HttpError(400, ErrorCode.VALIDATION_ERROR, "stepId must be an integer");
  }
  return id;
}

recipeRouter.get(
  "/",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const input = listRecipesSchema.parse(req.query);
    const { id: viewerId, houseId } = res.locals.userProfile;
    res.status(200).json(
      await listRecipes(viewerId, houseId, input.tab, {
        search: input.search,
        suitableForHousehold: input.suitableForHousehold,
        ingredientIds: input.ingredientIds,
        dietIds: input.dietIds,
      }),
    );
  }),
);

recipeRouter.get(
  "/:id",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const { id: viewerId, houseId } = res.locals.userProfile;
    res.status(200).json(await getRecipe(id, viewerId, houseId));
  }),
);

recipeRouter.post(
  "/",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const input = createRecipeSchema.parse(req.body);
    const { id: authorId, houseId } = res.locals.userProfile;
    res.status(201).json(await createRecipe(input, authorId, houseId));
  }),
);

recipeRouter.patch(
  "/:id",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const input = updateRecipeSchema.parse(req.body);
    const { id: viewerId, houseId } = res.locals.userProfile;
    res.status(200).json(await updateRecipe(id, input, viewerId, houseId));
  }),
);

recipeRouter.delete(
  "/:id",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const { id: viewerId, houseId } = res.locals.userProfile;
    await deleteRecipe(id, viewerId, houseId);
    res.status(204).end();
  }),
);

recipeRouter.post(
  "/:id/favorite",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const { id: viewerId, houseId } = res.locals.userProfile;
    await addFavorite(id, viewerId, houseId);
    res.status(204).end();
  }),
);

recipeRouter.delete(
  "/:id/favorite",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    await removeFavorite(id, res.locals.userProfile.id);
    res.status(204).end();
  }),
);

// Open to any authenticated viewer who can see the recipe, not just its
// author — see recipe-tech-step-correction.service.ts's own doc comment
// for why.
recipeRouter.post(
  "/:id/steps/:stepId/corrections",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const stepId = parseStepId(req.params.stepId);
    const input = submitTechStepCorrectionSchema.parse(req.body);
    const { id: correctorId, houseId } = res.locals.userProfile;
    res.status(201).json(await submitTechStepCorrection(id, stepId, input, correctorId, houseId));
  }),
);

recipeRouter.get(
  "/:id/steps/:stepId/corrections",
  requireAuth,
  wrapAsyncHandler<unknown, AuthLocals>(async (req, res) => {
    const id = parseRecipeId(req.params.id);
    const stepId = parseStepId(req.params.stepId);
    const { id: viewerId, houseId } = res.locals.userProfile;
    res.status(200).json(await listTechStepCorrections(id, stepId, viewerId, houseId));
  }),
);
