import fp from 'fastify-plugin';
import { asClass, Lifetime } from 'awilix';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod/v4';
import {
  CreateMealPlanSchema,
  UpdateMealPlanSchema,
  MealPlanQuerySchema,
  MealPlanResponseSchema,
  MealPlanListResponseSchema,
  MealPlanStatus,
} from '@meshitrack/shared';
import { MealPlanRepository } from './meal-plans.repository.js';
import { MealPlanService } from './meal-plans.service.js';
import { SuggestionEngineService } from './suggestion-engine.service.js';
import { ShoppingGapService } from './shopping-gap.service.js';
import { RecipesRepository } from '../recipes/recipes.repository.js';
import { PantryRepository } from '../pantry/pantry.repository.js';
import { NutritionTargetRepository } from '../nutrition-targets/nutrition-target.repository.js';
import { ProductsRepository } from '../products/products.repository.js';

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

type AnyMealDoc = {
  id: string;
  type: string;
  recipeId?: any;
  recipeName: string;
  servings: number;
  customName?: string | null;
  customNutrition?: AnyNutritionInfo | null;
  perServingNutrition: AnyNutritionInfo;
  notes?: string | null;
};

type AnyDayDoc = {
  date: string;
  meals: AnyMealDoc[];
  dailyNutritionTotal: AnyNutritionInfo;
};

type AnyPlanDoc = {
  _id: string | { toString: () => string };
  householdId: string;
  weekStartDate: string;
  days: AnyDayDoc[];
  status: string;
  shoppingListId?: string | null;
  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 cleanNutrition(n: AnyNutritionInfo) {
  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 toPlanResponse(doc: AnyPlanDoc): z.infer<typeof MealPlanResponseSchema> {
  return {
    _id: toStr(doc._id),
    householdId: doc.householdId,
    weekStartDate: doc.weekStartDate,
    days: doc.days.map((day) => ({
      date: day.date,
      meals: day.meals.map((meal) => ({
        id: meal.id,
        type: meal.type as any,
        ...(meal.recipeId ? { recipeId: toStr(meal.recipeId) } : {}),
        recipeName: meal.recipeName,
        servings: meal.servings,
        ...(meal.customName ? { customName: meal.customName } : {}),
        ...(meal.customNutrition ? { customNutrition: cleanNutrition(meal.customNutrition) } : {}),
        perServingNutrition: cleanNutrition(meal.perServingNutrition),
        ...(meal.notes ? { notes: meal.notes } : {}),
      })),
      dailyNutritionTotal: cleanNutrition(day.dailyNutritionTotal),
    })),
    status: doc.status as MealPlanStatus,
    ...(doc.shoppingListId ? { shoppingListId: doc.shoppingListId } : {}),
    createdBy: doc.createdBy,
    createdAt: toIso(doc.createdAt),
    updatedAt: toIso(doc.updatedAt),
  };
}

declare module '@fastify/awilix' {
  interface Cradle {
    mealPlanRepository: MealPlanRepository;
    mealPlanService: MealPlanService;
    suggestionEngineService: SuggestionEngineService;
    shoppingGapService: ShoppingGapService;
  }
}

export default fp(
  async (fastify) => {
    // Ensure prerequisite repositories from other modules are registered if not already done
    // Fastify modules run sequentially, but it's safe to register singletons if they're missing in Awilix.
    fastify.diContainer.register({
      mealPlanRepository: asClass(MealPlanRepository, { lifetime: Lifetime.SINGLETON }),
      mealPlanService: asClass(MealPlanService, { lifetime: Lifetime.SINGLETON }),
      suggestionEngineService: asClass(SuggestionEngineService, { lifetime: Lifetime.SINGLETON }),
      shoppingGapService: asClass(ShoppingGapService, { lifetime: Lifetime.SINGLETON }),

      // Make sure other module deps are available for classes instantiated by SuggestionEngine/ShoppingGap
      recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }),
      pantryRepository: asClass(PantryRepository, { lifetime: Lifetime.SINGLETON }),
      nutritionTargetRepository: asClass(NutritionTargetRepository, {
        lifetime: Lifetime.SINGLETON,
      }),
      productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }),
    });

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

    // GET /api/v1/households/:householdId/meal-plans — query/paginate list
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/meal-plans',
      schema: {
        params: householdParams,
        querystring: MealPlanQuerySchema,
        response: { 200: MealPlanListResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const result = await service.list(request.params.householdId, request.query);
        return reply.send({
          data: result.data.map(toPlanResponse),
          pagination: result.pagination,
        });
      },
    });

    // GET /api/v1/households/:householdId/meal-plans/suggestions — get algorithmic recommendations
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/meal-plans/suggestions',
      schema: {
        params: householdParams,
        querystring: z.object({
          limit: z.coerce.number().int().min(1).max(50).default(5),
        }),
        response: {
          200: z.array(
            z.object({
              recipeId: z.string(),
              recipeName: z.string(),
              totalScore: z.number(),
              scores: z.object({
                coverage: z.number(),
                urgency: z.number(),
                nutrition: z.number(),
                variety: z.number(),
              }),
              reasoning: z.array(z.string()),
            }),
          ),
        },
      },
      handler: async (request, reply) => {
        const engine = fastify.diContainer.resolve('suggestionEngineService');
        const suggestions = await engine.getSuggestions(
          request.params.householdId,
          request.user.keycloakId,
          { limit: request.query.limit },
        );
        return reply.send(suggestions);
      },
    });

    // GET /api/v1/households/:householdId/meal-plans/week/:weekStartDate — fetch exactly by week start date
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/meal-plans/week/:weekStartDate',
      schema: {
        params: householdParams.extend({
          weekStartDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        }),
        response: {
          200: z.union([
            MealPlanResponseSchema,
            z.object({ message: z.literal('No meal plan scheduled for this week') }),
          ]),
        },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const plan = await service.getByWeek(
          request.params.householdId,
          request.params.weekStartDate,
        );
        if (!plan) {
          return reply.status(200).send({ message: 'No meal plan scheduled for this week' });
        }
        return reply.send(toPlanResponse(plan as AnyPlanDoc));
      },
    });

    // GET /api/v1/households/:householdId/meal-plans/:id — fetch by ID
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/meal-plans/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        response: { 200: MealPlanResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const plan = await service.getById(request.params.id, request.params.householdId);
        return reply.send(toPlanResponse(plan as AnyPlanDoc));
      },
    });

    // POST /api/v1/households/:householdId/meal-plans — create a new weekly plan
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/meal-plans',
      schema: {
        params: householdParams,
        body: CreateMealPlanSchema,
        response: { 201: MealPlanResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const plan = await service.create(
          request.params.householdId,
          request.user.keycloakId,
          request.body,
        );
        return reply.status(201).send(toPlanResponse(plan as AnyPlanDoc));
      },
    });

    // PATCH /api/v1/households/:householdId/meal-plans/:id — granular structural updates
    app.route({
      method: 'PATCH',
      url: '/api/v1/households/:householdId/meal-plans/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        body: UpdateMealPlanSchema,
        response: { 200: MealPlanResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const plan = await service.update(
          request.params.id,
          request.params.householdId,
          request.body,
        );
        return reply.send(toPlanResponse(plan as AnyPlanDoc));
      },
    });

    // PATCH /api/v1/households/:householdId/meal-plans/:id/status — simple status transition (draft -> active -> archived)
    app.route({
      method: 'PATCH',
      url: '/api/v1/households/:householdId/meal-plans/:id/status',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        body: z.object({
          status: z.nativeEnum(MealPlanStatus),
        }),
        response: { 200: MealPlanResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('mealPlanService');
        const plan = await service.updateStatus(
          request.params.id,
          request.params.householdId,
          request.body.status,
        );
        return reply.send(toPlanResponse(plan as AnyPlanDoc));
      },
    });

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

    // GET /api/v1/households/:householdId/meal-plans/:id/gap — report shopping deficiencies
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/meal-plans/:id/gap',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        response: {
          200: z.object({
            mealPlanId: z.string(),
            missingItems: z.array(
              z.object({
                productId: z.string(),
                productName: z.string(),
                category: z.string(),
                requiredQuantity: z.number(),
                pantryQuantity: z.number(),
                missingQuantity: z.number(),
                unit: z.string(),
              }),
            ),
          }),
        },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('shoppingGapService');
        const result = await service.calculateGap(request.params.householdId, request.params.id);
        return reply.send(result);
      },
    });
  },
  {
    name: 'meal-plans-routes',
    dependencies: ['auth-plugin'],
  },
);
