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 { PricingService } from '../application/pricing-service.js';
import { InvalidPriceError, PriceNotFoundError } from '../domain/errors.js';
import type { PriceCalculation, VariantPrice } from '../domain/price.js';
import { PgPricingRepository } from '../infrastructure/pg-pricing-repository.js';

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

const variantParamSchema = z.object({ variantId: z.uuid() });
const vatRateSchema = z.enum(['general', 'reduced', 'super-reduced']);
const setPriceBodySchema = z.object({
  netUnitAmountCents: z.number().int().min(0),
  offerCents: z.number().int().min(0).nullable().optional(),
  costCents: z.number().int().min(0).nullable().optional(),
  vatRate: vatRateSchema,
});
const calculateBodySchema = z
  .object({
    variantId: z.uuid(),
    quantity: z.number().int().positive(),
  })
  .strip();

export async function registerPricingRoutes(
  app: FastifyInstance,
  deps: PricingRoutesDeps,
): Promise<void> {
  const pricing = new PricingService(new PgPricingRepository(deps.pool));

  const getPriceSchema: FastifySchema = {
    tags: ['Pricing'],
    summary: 'Get variant price (público)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    response: { 404: errorSchema },
  };
  app.get('/pricing/variants/:variantId', { schema: getPriceSchema }, async (request, reply) => {
    const { variantId } = parseJson(variantParamSchema, request.params);
    const price = await pricing.getVariantPrice(variantId);
    if (!price) {
      throw new AppError(404, 'PRICING_PRICE_NOT_FOUND', 'Variant price not found');
    }
    return reply.send(serializeVariantPrice(price));
  });

  const setPriceSchema: FastifySchema = {
    tags: ['Pricing'],
    summary: 'Set variant price (admin)',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.put('/pricing/variants/:variantId', { schema: setPriceSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { variantId } = parseJson(variantParamSchema, request.params);
    const input = parseJson(setPriceBodySchema, request.body);
    try {
      const price = await pricing.setVariantPrice({ variantId, ...input });
      return reply.send(serializeVariantPrice(price));
    } catch (error) {
      throw mapPricingError(error);
    }
  });

  const calcSchema: FastifySchema = {
    tags: ['Pricing'],
    summary: 'Calculate price (público)',
    description: 'Calcula el precio con IVA para una variante y cantidad.',
    body: { type: 'object' },
  };
  app.post('/pricing/calculate', { schema: calcSchema }, async (request, reply) => {
    const input = parseJson(calculateBodySchema, request.body);
    try {
      const calculation = await pricing.calculate(input);
      return reply.send(serializeCalculation(calculation));
    } catch (error) {
      throw mapPricingError(error);
    }
  });

  // ── Admin tax rates ─────────────────────────────────────────────────────────
  const listTaxSchema: FastifySchema = {
    tags: ['Pricing'],
    summary: 'List tax rates (admin)',
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.get('/admin/tax-rates', { schema: listTaxSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const result = await deps.pool.query<{
      id: string;
      name: string;
      rate_percent: number;
      country: string;
      applies_to: string;
      active: boolean;
    }>('SELECT * FROM tax_rates ORDER BY rate_percent ASC');
    return reply.send({
      items: result.rows.map((r) => ({
        id: r.id,
        name: r.name,
        ratePercent: Number(r.rate_percent),
        country: r.country,
        appliesTo: r.applies_to,
        active: r.active,
      })),
    });
  });

  const patchTaxSchema: FastifySchema = {
    tags: ['Pricing'],
    summary: 'Update tax rate (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.patch('/admin/tax-rates/:id', { schema: patchTaxSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const id = (request.params as { id: string }).id;
    const patch = parseJson(
      z.object({
        name: z.string().min(1).max(40).optional(),
        ratePercent: z.number().min(0).max(100).optional(),
        active: z.boolean().optional(),
        appliesTo: z.enum(['general', 'reduced', 'super-reduced']).optional(),
      }),
      request.body,
    );
    const sets: string[] = [];
    const values: unknown[] = [];
    let i = 1;
    if (patch.name !== undefined) {
      sets.push(`name = $${i++}`);
      values.push(patch.name);
    }
    if (patch.ratePercent !== undefined) {
      sets.push(`rate_percent = $${i++}`);
      values.push(patch.ratePercent);
    }
    if (patch.active !== undefined) {
      sets.push(`active = $${i++}`);
      values.push(patch.active);
    }
    if (patch.appliesTo !== undefined) {
      sets.push(`applies_to = $${i++}`);
      values.push(patch.appliesTo);
    }
    if (!sets.length) return reply.send({ ok: true });
    values.push(id);
    await deps.pool.query(`UPDATE tax_rates SET ${sets.join(', ')} WHERE id = $${i}`, values);
    return reply.send({ ok: true });
  });
}

function mapPricingError(error: unknown): Error {
  if (error instanceof PriceNotFoundError) {
    return new AppError(404, 'PRICING_PRICE_NOT_FOUND', error.message);
  }
  if (error instanceof InvalidPriceError) {
    return new AppError(422, 'INVALID_PRICE', error.message);
  }
  return error instanceof Error ? error : new Error('Unknown pricing error');
}

function serializeVariantPrice(price: VariantPrice) {
  return {
    variantId: price.variantId,
    netUnitAmountCents: price.netUnitAmountCents,
    offerCents: price.offerCents,
    costCents: price.costCents,
    vatRate: price.vatRate,
    currency: price.currency,
    createdAt: price.createdAt.toISOString(),
    updatedAt: price.updatedAt.toISOString(),
  };
}

function serializeCalculation(calculation: PriceCalculation) {
  return {
    variantId: calculation.variantId,
    quantity: calculation.quantity,
    currency: calculation.currency,
    vatRate: calculation.vatRate,
    vatBasisPoints: calculation.vatBasisPoints,
    netUnitAmountCents: calculation.netUnitAmountCents,
    netSubtotalCents: calculation.netSubtotalCents,
    vatAmountCents: calculation.vatAmountCents,
    totalCents: calculation.totalCents,
  };
}
