import type { FastifyInstance } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
import { requireRole, type Authenticate } from '../../../shared/auth.js';
import { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import { InventoryService } from '../application/inventory-service.js';
import {
  InsufficientReservedStockError,
  InsufficientStockError,
  InvalidStockQuantityError,
} from '../domain/errors.js';
import type { SetAvailableStockCommand, StockItem } from '../domain/stock.js';
import { DEFAULT_STORE_ID } from '../domain/stock.js';
import { PgInventoryRepository } from '../infrastructure/pg-inventory-repository.js';

export interface InventoryRoutesDeps {
  pool: pg.Pool;
  authenticate: Authenticate;
}

const variantParamSchema = z.object({ variantId: z.uuid() });
const availabilityQuerySchema = z.object({
  quantity: z.coerce.number().int().positive().default(1),
  storeId: z.uuid().optional(),
});
const stockBodySchema = z.object({
  quantity: z.number().int().min(0),
  storeId: z.uuid().optional(),
});
const stockCommandBodySchema = z.object({
  quantity: z.number().int().positive(),
  storeId: z.uuid().optional(),
});

const bulkAdjustItemSchema = z.object({
  variantId: z.uuid(),
  quantity: z.number().int().min(0),
});
const bulkAdjustBodySchema = z.object({
  items: z.array(bulkAdjustItemSchema).min(1).max(100),
});

export async function registerInventoryRoutes(
  app: FastifyInstance,
  deps: InventoryRoutesDeps,
): Promise<void> {
  const inventory = new InventoryService(new PgInventoryRepository(deps.pool));

  const availabilitySchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Check availability (público)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    querystring: {
      type: 'object',
      properties: {
        quantity: { type: 'integer', default: 1 },
        storeId: { type: 'string', format: 'uuid' },
      },
    },
  };
  app.get(
    '/inventory/:variantId/availability',
    { schema: availabilitySchema },
    async (request, reply) => {
      const { variantId } = parseJson(variantParamSchema, request.params);
      const { quantity, storeId } = parseJson(availabilityQuerySchema, request.query);
      const availability = await inventory.checkAvailability(
        variantId,
        storeId ?? DEFAULT_STORE_ID,
        quantity,
      );
      return reply.send(availability);
    },
  );

  const setStockSchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Set available stock (admin)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: {
      type: 'object',
      required: ['quantity'],
      properties: { quantity: { type: 'integer', minimum: 0 } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.put('/inventory/:variantId/stock', { schema: setStockSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { variantId } = parseJson(variantParamSchema, request.params);
    const { quantity, storeId } = parseJson(stockBodySchema, request.body);
    try {
      const item = await inventory.setAvailable({
        variantId,
        storeId: storeId ?? DEFAULT_STORE_ID,
        quantity,
      });
      return reply.send(serializeStockItem(item));
    } catch (error) {
      throw mapInventoryError(error);
    }
  });

  const reserveSchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Reserve stock (admin)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: {
      type: 'object',
      required: ['quantity'],
      properties: { quantity: { type: 'integer', minimum: 1 } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post(
    '/inventory/:variantId/reservations',
    { schema: reserveSchema },
    async (request, reply) => {
      const user = await deps.authenticate(request);
      requireRole(user, 'admin');
      const { variantId } = parseJson(variantParamSchema, request.params);
      const { quantity, storeId } = parseJson(stockCommandBodySchema, request.body);
      try {
        const item = await inventory.reserve({
          variantId,
          storeId: storeId ?? DEFAULT_STORE_ID,
          quantity,
        });
        return reply.code(201).send(serializeStockItem(item));
      } catch (error) {
        throw mapInventoryError(error);
      }
    },
  );

  const releaseSchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Release reservation (admin)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: {
      type: 'object',
      required: ['quantity'],
      properties: { quantity: { type: 'integer', minimum: 1 } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post(
    '/inventory/:variantId/reservations/release',
    { schema: releaseSchema },
    async (request, reply) => {
      const user = await deps.authenticate(request);
      requireRole(user, 'admin');
      const { variantId } = parseJson(variantParamSchema, request.params);
      const { quantity, storeId } = parseJson(stockCommandBodySchema, request.body);
      try {
        const item = await inventory.release({
          variantId,
          storeId: storeId ?? DEFAULT_STORE_ID,
          quantity,
        });
        return reply.send(serializeStockItem(item));
      } catch (error) {
        throw mapInventoryError(error);
      }
    },
  );

  const confirmSchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Confirm reservation (admin)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: {
      type: 'object',
      required: ['quantity'],
      properties: { quantity: { type: 'integer', minimum: 1 } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post(
    '/inventory/:variantId/reservations/confirm',
    { schema: confirmSchema },
    async (request, reply) => {
      const user = await deps.authenticate(request);
      requireRole(user, 'admin');
      const { variantId } = parseJson(variantParamSchema, request.params);
      const { quantity, storeId } = parseJson(stockCommandBodySchema, request.body);
      try {
        const item = await inventory.confirm({
          variantId,
          storeId: storeId ?? DEFAULT_STORE_ID,
          quantity,
        });
        return reply.send(serializeStockItem(item));
      } catch (error) {
        throw mapInventoryError(error);
      }
    },
  );

  // POST /inventory/bulk-adjust — atomic bulk stock adjustment
  const bulkSchema: FastifySchema = {
    tags: ['Inventory'],
    summary: 'Bulk stock adjustment (admin)',
    description: 'Ajuste atómico de stock para múltiples variantes en una transacción.',
    body: {
      type: 'object',
      required: ['items'],
      properties: {
        items: {
          type: 'array',
          items: {
            type: 'object',
            required: ['variantId', 'quantity'],
            properties: {
              variantId: { type: 'string', format: 'uuid' },
              quantity: { type: 'integer', minimum: 0 },
            },
          },
        },
      },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post('/inventory/bulk-adjust', { schema: bulkSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { items } = parseJson(bulkAdjustBodySchema, request.body);

    const client = await deps.pool.connect();
    try {
      await client.query('BEGIN');
      const results: StockItem[] = [];

      for (const item of items) {
        const command: SetAvailableStockCommand = {
          variantId: item.variantId,
          storeId: DEFAULT_STORE_ID,
          quantity: item.quantity,
        };

        // Use direct SQL for atomicity within the transaction
        const result = await client.query<{
          id: string;
          variant_id: string;
          store_id: string;
          available: number;
          reserved: number;
          sold: number;
          incoming: number;
          created_at: Date;
          updated_at: Date;
        }>(
          `INSERT INTO inventory_stock (variant_id, store_id, available)
           VALUES ($1, $2, $3)
           ON CONFLICT (variant_id, store_id) DO UPDATE
             SET available = EXCLUDED.available, updated_at = now()
           RETURNING *`,
          [command.variantId, command.storeId, command.quantity],
        );

        // Log the movement
        await client.query(
          `INSERT INTO inventory_movements (variant_id, store_id, operation, quantity)
           VALUES ($1, $2, $3, $4)`,
          [command.variantId, command.storeId, 'bulk_adjust', command.quantity],
        );

        const row = result.rows[0];
        if (!row) {
          throw new Error(`Inventory stock not found for variant ${command.variantId}`);
        }
        results.push({
          id: row.id,
          variantId: row.variant_id,
          storeId: row.store_id,
          available: row.available,
          reserved: row.reserved,
          sold: row.sold,
          incoming: row.incoming,
          createdAt: row.created_at,
          updatedAt: row.updated_at,
        });
      }

      await client.query('COMMIT');
      return reply.code(201).send({ adjusted: results.map(serializeStockItem), errors: [] });
    } catch (error) {
      await client.query('ROLLBACK');
      throw mapInventoryError(error);
    } finally {
      client.release();
    }
  });
}

function mapInventoryError(error: unknown): Error {
  if (error instanceof InsufficientStockError) {
    return new AppError(409, 'INSUFFICIENT_STOCK', error.message);
  }
  if (error instanceof InsufficientReservedStockError) {
    return new AppError(409, 'INSUFFICIENT_RESERVED_STOCK', error.message);
  }
  if (error instanceof InvalidStockQuantityError) {
    return new AppError(422, 'INVALID_STOCK_QUANTITY', error.message);
  }
  return error instanceof Error ? error : new Error('Unknown inventory error');
}

function serializeStockItem(item: StockItem) {
  return {
    id: item.id,
    variantId: item.variantId,
    available: item.available,
    reserved: item.reserved,
    sold: item.sold,
    incoming: item.incoming,
    createdAt: item.createdAt.toISOString(),
    updatedAt: item.updatedAt.toISOString(),
  };
}
