import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { AppError } from '../utils/errors.js';
import { logger } from '../utils/logger.js';

interface AjvValidationError {
  instancePath: string;
  keyword: string;
  message?: string;
  params?: Record<string, unknown>;
}

// A hand-picked label for every field a schema validates, so messages read
// as "Password must be..." instead of "/password must be...". Falls back to
// a humanized version of the raw field name for anything not listed here.
const FIELD_LABELS: Record<string, string> = {
  name: 'Name',
  mobile: 'Mobile number',
  alternate_mobile: 'Alternate mobile number',
  email: 'Email',
  role_id: 'Role',
  password: 'Password',
  order_date: 'Order date',
  delivery_date: 'Delivery date',
  po_date: 'PO date',
  dispatch_date: 'Dispatch date',
  cancel_date: 'Cancel date',
  reason: 'Reason',
  comment: 'Comment',
};

// Patterns whose raw regex is meaningless to a user — swapped for the plain-
// English rule they're actually enforcing.
const PATTERN_HINTS: Record<string, string> = {
  password: 'must contain at least one letter and one number',
  mobile: 'must be a 10-digit mobile number starting with 6-9',
  alternate_mobile: 'must be a 10-digit mobile number starting with 6-9',
};

function fieldLabel(field: string): string {
  return FIELD_LABELS[field] ?? (field.charAt(0).toUpperCase() + field.slice(1).replace(/_/g, ' '));
}

function friendlyValidationMessage(err: AjvValidationError): string {
  const field = err.instancePath.replace(/^\//, '') || String(err.params?.missingProperty ?? '');
  const label = fieldLabel(field);

  switch (err.keyword) {
    case 'required':
      return `${fieldLabel(String(err.params?.missingProperty ?? field))} is required`;
    case 'minLength':
      return `${label} must be at least ${err.params?.limit} characters`;
    case 'maxLength':
      return `${label} must be at most ${err.params?.limit} characters`;
    case 'pattern':
      return `${label} ${PATTERN_HINTS[field] ?? 'is not in a valid format'}`;
    case 'format':
      return err.params?.format === 'email' ? `${label} must be a valid email address` : `${label} must be a valid ${err.params?.format}`;
    case 'enum':
      return `${label} must be one of: ${(err.params?.allowedValues as unknown[] | undefined)?.join(', ')}`;
    case 'type':
      return `${label} must be a ${err.params?.type}`;
    default:
      return `${label} ${err.message ?? 'is invalid'}`;
  }
}

export function registerErrorHandler(fastify: FastifyInstance) {
  fastify.setErrorHandler((error: AppError | Error, _request: FastifyRequest, reply: FastifyReply) => {
    if (error instanceof AppError) {
      return reply.status(error.statusCode).send({ error: error.message });
    }

    const fastifyError = error as { validation?: AjvValidationError[]; statusCode?: number };
    if (fastifyError.validation) {
      const messages = fastifyError.validation.map(friendlyValidationMessage);
      return reply.status(400).send({
        error: messages.join('. '),
        details: messages,
      });
    }

    // Fastify itself and its official plugins (rate-limit, multipart, etc.)
    // throw plain errors with a `statusCode`, not an AppError — without this,
    // a 429 from @fastify/rate-limit (or a 413 from a payload-too-large
    // check, etc.) would fall through to the generic 500 below and hide
    // what actually happened from the client.
    if (typeof fastifyError.statusCode === 'number' && fastifyError.statusCode >= 400 && fastifyError.statusCode < 500) {
      return reply.status(fastifyError.statusCode).send({ error: error.message });
    }

    logger.error({ err: error }, 'Unhandled error');
    
    if (process.env.NODE_ENV === 'development') {
      return reply.status(500).send({
        error: error.message,
        stack: error.stack,
        details: error
      });
    }

    return reply.status(500).send({ error: 'Internal server error' });
  });
}
