import fp from 'fastify-plugin';
import { asClass, Lifetime } from 'awilix';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod/v4';
import multipart from '@fastify/multipart';
import {
  CreateProductSchema,
  UpdateProductSchema,
  ProductQuerySchema,
  ProductResponseSchema,
  ProductListResponseSchema,
} from '@meshitrack/shared';
import { ProductsRepository } from './products.repository.js';
import { ProductsService } from './products.service.js';
import { BarcodeService } from './barcode.service.js';
import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from './csv-parser.js';

type AnyProductDoc = {
  _id: string | { toString: () => string };
  householdId: string;
  name: string;
  brand?: string | null;
  barcode?: string | null;
  category: string;
  servingSize: number;
  servingUnit: string;
  densityGPerMl?: number | null;
  nutrition: {
    calories: number;
    protein: number;
    carbs: number;
    fat: number;
    fiber?: number | null;
    sugar?: number | null;
    sodium?: number | null;
    saturatedFat?: number | null;
    cholesterol?: number | null;
  };
  tags: string[];
  imageUrl?: string | null;
  source: string;
  createdBy: string;
  createdAt: string | { toISOString: () => string };
  updatedAt: string | { toISOString: () => string };
  deletedAt?: string | { toISOString: () => string } | null;
};

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

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

function toProductResponse(doc: AnyProductDoc): z.infer<typeof ProductResponseSchema> {
  return {
    _id: toStr(doc._id),
    householdId: doc.householdId,
    name: doc.name,
    ...(doc.brand ? { brand: doc.brand } : {}),
    ...(doc.barcode ? { barcode: doc.barcode } : {}),
    category: doc.category,
    servingSize: doc.servingSize,
    servingUnit: doc.servingUnit,
    ...(doc.densityGPerMl != null ? { densityGPerMl: doc.densityGPerMl } : {}),
    nutrition: {
      calories: doc.nutrition.calories,
      protein: doc.nutrition.protein,
      carbs: doc.nutrition.carbs,
      fat: doc.nutrition.fat,
      ...(doc.nutrition.fiber != null ? { fiber: doc.nutrition.fiber } : {}),
      ...(doc.nutrition.sugar != null ? { sugar: doc.nutrition.sugar } : {}),
      ...(doc.nutrition.sodium != null ? { sodium: doc.nutrition.sodium } : {}),
      ...(doc.nutrition.saturatedFat != null ? { saturatedFat: doc.nutrition.saturatedFat } : {}),
      ...(doc.nutrition.cholesterol != null ? { cholesterol: doc.nutrition.cholesterol } : {}),
    },
    tags: doc.tags,
    ...(doc.imageUrl ? { imageUrl: doc.imageUrl } : {}),
    source: doc.source,
    createdBy: doc.createdBy,
    createdAt: toIso(doc.createdAt),
    updatedAt: toIso(doc.updatedAt),
    ...(doc.deletedAt ? { deletedAt: toIso(doc.deletedAt) } : {}),
  };
}

declare module '@fastify/awilix' {
  interface Cradle {
    productsRepository: ProductsRepository;
    productsService: ProductsService;
    barcodeService: BarcodeService;
  }
}

export default fp(
  async (fastify) => {
    fastify.diContainer.register({
      productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }),
      productsService: asClass(ProductsService, { lifetime: Lifetime.SINGLETON }),
      barcodeService: asClass(BarcodeService, { lifetime: Lifetime.SINGLETON }),
    });

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

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

    // GET /api/v1/households/:householdId/products/barcode/:code — barcode lookup
    app.route({
      method: 'GET',
      url: '/api/v1/households/:householdId/products/barcode/:code',
      schema: {
        params: householdParams.extend({ code: z.string() }),
        response: {
          200: ProductResponseSchema,
          404: z.object({ found: z.literal(false) }),
        },
      },
      handler: async (request, reply) => {
        const { householdId, code } = request.params;
        const service = fastify.diContainer.resolve('barcodeService');
        const result = await service.lookup(householdId, code, request.user.keycloakId);
        if (!result.found) {
          return reply.status(404).send({ found: false });
        }
        return reply.send(toProductResponse(result.product as AnyProductDoc));
      },
    });

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

    // POST /api/v1/households/:householdId/products — create
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/products',
      schema: {
        params: householdParams,
        body: CreateProductSchema,
        response: { 201: ProductResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('productsService');
        const product = await service.create(
          request.body,
          request.params.householdId,
          request.user.keycloakId,
        );
        return reply.status(201).send(toProductResponse(product));
      },
    });

    // PATCH /api/v1/households/:householdId/products/:id — update
    app.route({
      method: 'PATCH',
      url: '/api/v1/households/:householdId/products/:id',
      schema: {
        params: householdParams.extend({ id: z.string() }),
        body: UpdateProductSchema,
        response: { 200: ProductResponseSchema },
      },
      handler: async (request, reply) => {
        const service = fastify.diContainer.resolve('productsService');
        const product = await service.update(
          request.params.id,
          request.params.householdId,
          request.body,
        );
        return reply.send(toProductResponse(product));
      },
    });

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

    // POST /api/v1/households/:householdId/products/smart-add — LLM placeholder
    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/products/smart-add',
      schema: {
        params: householdParams,
        body: z.object({ text: z.string().optional() }),
        response: {
          200: z.object({ available: z.literal(false), message: z.string() }),
        },
      },
      handler: async (_request, reply) => {
        return reply.send({
          available: false,
          message: 'LLM not configured',
        });
      },
    });

    // POST /api/v1/households/:householdId/products/import — bulk CSV/JSON import
    await fastify.register(multipart, { limits: { fileSize: MAX_FILE_SIZE } });

    const ImportResponseSchema = z.object({
      imported: z.number(),
      skipped: z.number(),
      errors: z.array(z.object({ row: z.number(), message: z.string() })),
    });

    app.route({
      method: 'POST',
      url: '/api/v1/households/:householdId/products/import',
      schema: {
        params: householdParams,
        response: {
          200: ImportResponseSchema,
          400: z.object({ error: z.string() }),
        },
      },
      handler: async (request, reply) => {
        const file = await request.file();
        if (!file) {
          return reply.status(400).send({ error: 'No file uploaded' });
        }

        const buffer = await file.toBuffer();
        const content = buffer.toString('utf-8');
        const contentType = file.mimetype;

        let items: Parameters<ProductsService['importProducts']>[2];
        let parseErrors: { row: number; message: string }[] = [];

        if (contentType === 'application/json' || file.filename.endsWith('.json')) {
          try {
            const parsed = JSON.parse(content) as unknown;
            if (!Array.isArray(parsed)) {
              return reply.status(400).send({ error: 'JSON must be an array' });
            }
            items = parsed;
          } catch {
            return reply.status(400).send({ error: 'Invalid JSON' });
          }
        } else {
          const result = parseCsv(content);
          items = result.items;
          parseErrors = result.errors;
        }

        if (items.length > MAX_ROWS) {
          return reply.status(400).send({ error: `Maximum ${MAX_ROWS} rows allowed` });
        }

        const service = fastify.diContainer.resolve('productsService');
        const result = await service.importProducts(
          request.params.householdId,
          request.user.keycloakId,
          items,
        );

        return reply.send({
          imported: result.imported,
          skipped: result.skipped,
          errors: [...parseErrors, ...result.errors],
        });
      },
    });
  },
  {
    name: 'products-routes',
    dependencies: ['auth-plugin'],
  },
);
