import fp from 'fastify-plugin';
import { asClass, asValue, Lifetime } from 'awilix';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod/v4';
import {
  CreateRecipeSchema,
  UpdateRecipeSchema,
  RecipeQuerySchema,
  ScaleRecipeSchema,
  ImportRecipeTextSchema,
  ImportRecipeUrlSchema,
  RecipeResponseSchema,
  RecipeListResponseSchema,
} from '@meshitrack/shared';
import { RecipesRepository } from './recipes.repository.js';
import { RecipesService } from './recipes.service.js';
import { ProductsRepository } from '../products/products.repository.js';
import { NoOpLlmProvider } from '../llm/no-op-llm.provider.js';
import type { NutritionInfo } from '@meshitrack/shared';

type NullableNutritionInfo = {
  calories: number;
  protein: number;
  carbs: number;
  fat: number;
  fiber?: number | null;
  sugar?: number | null;
  sodium?: number | null;
  saturatedFat?: number | null;
  cholesterol?: number | null;
};

type AnyIngredient = {
  productId: string;
  productName: string;
  quantity: number;
  unit: string;
  originalQuantity?: number | null;
  originalUnit?: string | null;
  preparation?: string | null;
  isOptional: boolean;
  nutritionContribution: NullableNutritionInfo;
};

type AnyRecipeDoc = {
  _id: string | { toString: () => string };
  householdId: string;
  name: string;
  description?: string | null;
  servings: number;
  prepTime?: number | null;
  cookTime?: number | null;
  totalTime?: number | null;
  ingredients: AnyIngredient[];
  steps: Array<{
    order: number;
    instruction: string;
    duration?: number | null;
    tip?: string | null;
  }>;
  tags: string[];
  cuisine?: string | null;
  imageUrl?: string | null;
  source?: { type: string; url?: string | null; importedAt?: Date | string | null } | null;
  totalNutrition: NullableNutritionInfo;
  perServingNutrition: NullableNutritionInfo;
  warnings: string[];
  isFavorite: boolean;
  createdBy: string;
  createdAt: string | Date;
  updatedAt: string | Date;
};

function toStr(v: string | { toString: () => string }): string {
  return typeof v === 'string' ? v : v.toString();
}

function toIso(v: string | Date): string {
  return typeof v === 'string' ? v : v.toISOString();
}

function stripNullNutrition(n: NullableNutritionInfo): NutritionInfo {
  return {
    calories: n.calories,
    protein: n.protein,
    carbs: n.carbs,
    fat: n.fat,
    ...(n.fiber != null ? { fiber: n.fiber } : {}),
    ...(n.sugar != null ? { sugar: n.sugar } : {}),
    ...(n.sodium != null ? { sodium: n.sodium } : {}),
    ...(n.saturatedFat != null ? { saturatedFat: n.saturatedFat } : {}),
    ...(n.cholesterol != null ? { cholesterol: n.cholesterol } : {}),
  };
}

function toRecipeResponse(doc: AnyRecipeDoc): z.infer<typeof RecipeResponseSchema> {
  return {
    _id: toStr(doc._id),
    householdId: doc.householdId,
    name: doc.name,
    ...(doc.description ? { description: doc.description } : {}),
    servings: doc.servings,
    ...(doc.prepTime != null ? { prepTime: doc.prepTime } : {}),
    ...(doc.cookTime != null ? { cookTime: doc.cookTime } : {}),
    ...(doc.totalTime != null ? { totalTime: doc.totalTime } : {}),
    ingredients: doc.ingredients.map((ing) => ({
      productId: ing.productId,
      productName: ing.productName,
      quantity: ing.quantity,
      unit: ing.unit as 'g' | 'ml' | 'piece' | 'slice',
      ...(ing.originalQuantity != null ? { originalQuantity: ing.originalQuantity } : {}),
      ...(ing.originalUnit ? { originalUnit: ing.originalUnit as never } : {}),
      ...(ing.preparation ? { preparation: ing.preparation } : {}),
      isOptional: ing.isOptional,
      nutritionContribution: stripNullNutrition(ing.nutritionContribution),
    })),
    steps: doc.steps.map((s) => ({
      order: s.order,
      instruction: s.instruction,
      ...(s.duration != null ? { duration: s.duration } : {}),
      ...(s.tip ? { tip: s.tip } : {}),
    })),
    tags: doc.tags,
    ...(doc.cuisine ? { cuisine: doc.cuisine } : {}),
    ...(doc.imageUrl ? { imageUrl: doc.imageUrl } : {}),
    ...(doc.source
      ? {
          source: {
            type: doc.source.type as 'manual' | 'url' | 'llm_import' | 'text_import',
            ...(doc.source.url ? { url: doc.source.url } : {}),
            ...(doc.source.importedAt
              ? {
                  importedAt:
                    typeof doc.source.importedAt === 'string'
                      ? doc.source.importedAt
                      : (doc.source.importedAt as Date).toISOString(),
                }
              : {}),
          },
        }
      : {}),
    totalNutrition: stripNullNutrition(doc.totalNutrition),
    perServingNutrition: stripNullNutrition(doc.perServingNutrition),
    warnings: doc.warnings as never,
    isFavorite: doc.isFavorite,
    createdBy: doc.createdBy,
    createdAt: toIso(doc.createdAt),
    updatedAt: toIso(doc.updatedAt),
  };
}

declare module '@fastify/awilix' {
  interface Cradle {
    recipesRepository: RecipesRepository;
    productsRepository: ProductsRepository;
    recipesService: RecipesService;
  }
}

export default fp(
  async (fastify) => {
    fastify.diContainer.register({
      recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }),
      productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }),
      recipesService: asClass(RecipesService, { lifetime: Lifetime.SINGLETON }),
      llmProvider: asValue(new NoOpLlmProvider()),
    });

    const app = fastify.withTypeProvider<ZodTypeProvider>();
    const householdParams = z.object({ householdId: z.string() });

    // GET /api/v1/households/:householdId/recipes — list/search
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/recipes',
      schema: {
        params: householdParams,
        querystring: RecipeQuerySchema,
        response: { 200: RecipeListResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const result = await service.list(request.params.householdId, request.query);
        return reply.send({
          data: result.data.map(toRecipeResponse),
          pagination: result.pagination,
        });
      },
    });

    // GET /api/v1/households/:householdId/recipes/:id — get by id
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/recipes/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        response: { 200: RecipeResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const recipe = await service.getById(request.params.id, request.params.householdId);
        return reply.send(toRecipeResponse(recipe));
      },
    });

    // POST /api/v1/households/:householdId/recipes — create
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/recipes',
      schema: {
        params: householdParams,
        body: CreateRecipeSchema,
        response: { 201: RecipeResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const recipe = await service.create(
          request.body,
          request.params.householdId,
          request.user.keycloakId,
        );
        return reply.status(201).send(toRecipeResponse(recipe));
      },
    });

    // PATCH /api/v1/households/:householdId/recipes/:id — update
    app.route({
      method: 'PATCH',
      url: '/api/v1/households/:householdId/recipes/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        body: UpdateRecipeSchema,
        response: { 200: RecipeResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const recipe = await service.update(
          request.params.id,
          request.params.householdId,
          request.body,
        );
        return reply.send(toRecipeResponse(recipe));
      },
    });

    // DELETE /api/v1/households/:householdId/recipes/:id — soft delete
    app.route({
      method: 'DELETE',
      url: '/api/v1/households/:householdId/recipes/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        response: { 204: z.undefined() },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        await service.delete(request.params.id, request.params.householdId);
        return reply.status(204).send();
      },
    });

    // POST /api/v1/households/:householdId/recipes/:id/scale — scale preview
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/recipes/:id/scale',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        body: ScaleRecipeSchema,
        response: { 200: RecipeResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const scaled = await service.scale(
          request.params.id,
          request.params.householdId,
          request.body,
        );
        return reply.send(toRecipeResponse(scaled as AnyRecipeDoc));
      },
    });

    // POST /api/v1/households/:householdId/recipes/import-text — LLM import from text
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/recipes/import-text',
      schema: {
        params: householdParams,
        body: ImportRecipeTextSchema,
        response: {
          200: z.union([
            z.object({ available: z.literal(false) }),
            z.object({ available: z.literal(true), draft: z.unknown() }),
          ]),
        },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const result = await service.importFromText(request.body.text, request.params.householdId);
        return reply.send(result);
      },
    });

    // POST /api/v1/households/:householdId/recipes/import-url — LLM import from URL
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/recipes/import-url',
      schema: {
        params: householdParams,
        body: ImportRecipeUrlSchema,
        response: {
          200: z.union([
            z.object({ available: z.literal(false) }),
            z.object({ available: z.literal(true), draft: z.unknown() }),
          ]),
        },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const result = await service.importFromUrl(request.body.url, request.params.householdId);
        return reply.send(result);
      },
    });

    // GET /api/v1/households/:householdId/recipes/by-product/:productId
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/recipes/by-product/:productId',
      schema: {
        params: householdParams.extend({ productId: z.string() }),
        querystring: z.object({
          cursor: z.string().optional(),
          limit: z.coerce.number().int().min(1).max(100).default(20),
        }),
        response: { 200: RecipeListResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('recipesService');
        const result = await service.findByProduct(
          request.params.householdId,
          request.params.productId,
          request.query,
        );
        return reply.send({
          data: result.data.map(toRecipeResponse),
          pagination: result.pagination,
        });
      },
    });
  },
  {
    name: 'recipes-routes',
    dependencies: ['auth-plugin'],
  },
);
