import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import type { Response } from 'express';
import { AppException, type AppErrorResponse } from './app.exception';
import { ErrorCode, type ErrorCodeValue } from './error-codes';

interface ClientErrorBody {
  success: false;
  error: {
    code: ErrorCodeValue;
    statusCode: number;
    details?: unknown;
  };
}

const STATUS_FALLBACK_CODES: Partial<Record<number, ErrorCodeValue>> = {
  [HttpStatus.BAD_REQUEST]: ErrorCode.BAD_REQUEST,
  [HttpStatus.UNAUTHORIZED]: ErrorCode.AUTH_UNAUTHORIZED,
  [HttpStatus.FORBIDDEN]: ErrorCode.PERMISSION_DENIED,
  [HttpStatus.NOT_FOUND]: ErrorCode.NOT_FOUND,
  [HttpStatus.CONFLICT]: ErrorCode.CONFLICT,
  [HttpStatus.INTERNAL_SERVER_ERROR]: ErrorCode.INTERNAL_ERROR,
};

/** Maps legacy English messages to stable codes during migration. */
const LEGACY_MESSAGE_CODES: Record<string, ErrorCodeValue> = {
  'Invalid credentials': ErrorCode.AUTH_INVALID_CREDENTIALS,
  Unauthorized: ErrorCode.AUTH_UNAUTHORIZED,
  'Refresh token not found': ErrorCode.AUTH_REFRESH_TOKEN_NOT_FOUND,
  'Invalid or expired refresh token': ErrorCode.AUTH_REFRESH_TOKEN_INVALID,
  'Invalid refresh token': ErrorCode.AUTH_REFRESH_TOKEN_INVALID,
  'Refresh token failed': ErrorCode.AUTH_REFRESH_TOKEN_INVALID,
  'Invalid token type': ErrorCode.AUTH_SESSION_EXPIRED,
  'Invalid token': ErrorCode.AUTH_SESSION_EXPIRED,
  'Session not found or expired': ErrorCode.AUTH_SESSION_EXPIRED,
  'User not found': ErrorCode.AUTH_USER_NOT_FOUND,
  'Organization is not selected': ErrorCode.AUTH_ORG_NOT_SELECTED,
  'Access denied to this organization': ErrorCode.AUTH_ORG_ACCESS_DENIED,
  'Your invitation is still pending activation': ErrorCode.AUTH_INVITATION_PENDING,
  'Auto-login failed': ErrorCode.AUTH_REGISTRATION_FAILED,
  'User already exists. Please login and create a new organization from your account.':
    ErrorCode.AUTH_REGISTRATION_EMAIL_EXISTS,
  'This mobile number is already registered.': ErrorCode.AUTH_REGISTRATION_MOBILE_EXISTS,
  'Please enter a valid mobile number': ErrorCode.AUTH_REGISTRATION_MOBILE_INVALID,
  'Current password is incorrect': ErrorCode.AUTH_PASSWORD_INCORRECT,
  'Password reset verification expired. Please verify your mobile again.':
    ErrorCode.AUTH_PASSWORD_RESET_EXPIRED,
  'Invalid or expired verification code': ErrorCode.AUTH_VERIFICATION_CODE_INVALID,
  'Please wait before requesting another code': ErrorCode.AUTH_VERIFICATION_RATE_LIMIT,
  'Current password is required': ErrorCode.AUTH_PASSWORD_CURRENT_REQUIRED,
  'Select an organization before creating a new one.': ErrorCode.AUTH_SELECT_ORG_FIRST,
  'Only owners of the current organization can create new organizations.':
    ErrorCode.AUTH_CREATE_ORG_OWNER_ONLY,
  'This action is only available for clinic organizations': ErrorCode.PERMISSION_CLINIC_ONLY,
  'This action is only available for lab organizations': ErrorCode.PERMISSION_LAB_ONLY,
  'Organization not found': ErrorCode.PERMISSION_ORG_NOT_FOUND,
  'Unknown organization type': ErrorCode.PERMISSION_DENIED,
  'You do not have permission to manage organizations': ErrorCode.PERMISSION_ORG_MANAGE,
  'You are not a member of this organization': ErrorCode.PERMISSION_NOT_MEMBER,
  'Only organization owners can manage participation': ErrorCode.PERMISSION_OWNER_ONLY,
  'Only organization owners can manage their working hours': ErrorCode.PERMISSION_OWNER_ONLY,
  'An active subscription is required to participate in treatments or tasks':
    ErrorCode.PERMISSION_PARTICIPATION_SUBSCRIPTION,
  'Enable treatment participation before setting working hours':
    ErrorCode.PERMISSION_ENABLE_PARTICIPATION_FIRST,
  'Working hours are only available for clinic organizations':
    ErrorCode.PERMISSION_CLINIC_WORKING_HOURS,
  'You do not have access to appointments': ErrorCode.PERMISSION_ACCESS_APPOINTMENTS,
  'You cannot create or modify appointments': ErrorCode.PERMISSION_EDIT_APPOINTMENTS,
  'You do not have access to treatments': ErrorCode.PERMISSION_ACCESS_TREATMENTS,
  'You cannot edit treatments': ErrorCode.PERMISSION_EDIT_TREATMENTS,
  'You do not have access to tasks': ErrorCode.PERMISSION_ACCESS_TASKS,
  'You do not have access to staff management': ErrorCode.PERMISSION_ACCESS_STAFF,
  'You cannot manage staff working hours': ErrorCode.PERMISSION_EDIT_STAFF,
};

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(HttpExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    const { statusCode, body } = this.normalizeException(exception);

    if (statusCode >= 500) {
      this.logger.error(
        exception instanceof Error ? exception.stack : String(exception),
      );
    }

    response.status(statusCode).json(body);
  }

  private normalizeException(exception: unknown): {
    statusCode: number;
    body: ClientErrorBody;
  } {
    if (!(exception instanceof HttpException)) {
      return {
        statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
        body: this.buildBody(ErrorCode.INTERNAL_ERROR, HttpStatus.INTERNAL_SERVER_ERROR),
      };
    }

    const statusCode = exception.getStatus();
    const rawResponse = exception.getResponse();

    if (exception instanceof AppException || this.isAppErrorResponse(rawResponse)) {
      const appError = rawResponse as AppErrorResponse;
      return {
        statusCode,
        body: this.buildBody(appError.code, statusCode, appError.details),
      };
    }

    const code = this.resolveLegacyCode(rawResponse, statusCode);
    return {
      statusCode,
      body: this.buildBody(code, statusCode),
    };
  }

  private isAppErrorResponse(value: unknown): value is AppErrorResponse {
    return (
      typeof value === 'object' &&
      value !== null &&
      'code' in value &&
      typeof (value as AppErrorResponse).code === 'string'
    );
  }

  private resolveLegacyCode(rawResponse: string | object, statusCode: number): ErrorCodeValue {
    const message = this.extractMessage(rawResponse);

    if (typeof message === 'string') {
      const mapped = LEGACY_MESSAGE_CODES[message.trim()];
      if (mapped) {
        return mapped;
      }
    }

    if (Array.isArray(message)) {
      return ErrorCode.VALIDATION_FAILED;
    }

    return STATUS_FALLBACK_CODES[statusCode] ?? ErrorCode.INTERNAL_ERROR;
  }

  private extractMessage(rawResponse: string | object): string | string[] | undefined {
    if (typeof rawResponse === 'string') {
      return rawResponse;
    }

    if (typeof rawResponse === 'object' && rawResponse !== null && 'message' in rawResponse) {
      const message = (rawResponse as { message?: string | string[] }).message;
      return message;
    }

    return undefined;
  }

  private buildBody(
    code: ErrorCodeValue,
    statusCode: number,
    details?: unknown,
  ): ClientErrorBody {
    return {
      success: false,
      error: {
        code,
        statusCode,
        ...(details !== undefined ? { details } : {}),
      },
    };
  }
}
