import { z } from 'zod';

/* eslint-disable @typescript-eslint/no-explicit-any */

/**
 * Error thrown when parameter validation fails
 */
export class ZodValidationError extends Error {
  constructor(
    public parameterIndex: number,
    public parameterName: string | undefined,
    public zodError: z.ZodError,
  ) {
    const paramInfo = parameterName ? `"${parameterName}"` : `at index ${parameterIndex}`;
    super(`Parameter validation failed for parameter ${paramInfo}: ${zodError.message}`);
    this.name = 'ZodValidationError';
  }
}

/**
 * Method decorator that validates method parameters using Zod schemas.
 * Each parameter that needs validation should have a corresponding schema in the schemas array.
 *
 * @param schemas Array of Zod schemas for validating parameters. Use `undefined` for parameters that don't need validation.
 * @param parameterNames Optional array of parameter names for better error messages
 * @example
 * ```typescript
 * class UserService {
 *   // Basic parameter validation
 *   @ValidateParams([z.string().email(), z.object({ name: z.string(), age: z.number().min(0) })])
 *   createUser(email: string, profile: UserProfile) {
 *     // Method implementation
 *   }
 *
 *   // With custom parameter names and optional validation
 *   @ValidateParams(
 *     [z.string().uuid(), z.object({ name: z.string().min(1) }), undefined],
 *     ['userId', 'userUpdate', 'options']
 *   )
 *   updateUser(id: string, data: Partial<User>, options?: any) {
 *     // Method implementation
 *   }
 * }
 * ```
 */
export function ValidateParams(
  schemas: Array<z.ZodSchema<any> | undefined>,
  parameterNames?: string[]
): MethodDecorator {
  return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    if (typeof originalMethod !== 'function') {
      return descriptor;
    }

    descriptor.value = function (this: any, ...args: any[]) {
      // Validate each parameter that has a schema
      for (let i = 0; i < schemas.length; i++) {
        const schema = schemas[i];
        if (schema) {
          const paramValue = args[i];
          const parameterName = parameterNames?.[i];

          try {
            // Use safeParse to avoid throwing errors and get detailed error info
            const result = schema.safeParse(paramValue);
            if (!result.success) {
              throw new ZodValidationError(
                i,
                parameterName,
                result.error
              );
            }
            // Replace the argument with the parsed/transformed value
            args[i] = result.data;
          } catch (error) {
            if (error instanceof ZodValidationError) {
              throw error;
            } else if (error instanceof z.ZodError) {
              throw new ZodValidationError(
                i,
                parameterName,
                error
              );
            } else {
              // Re-throw unexpected errors
              throw error;
            }
          }
        }
      }

      // Call the original method with validated parameters
      return originalMethod.apply(this, args);
    };

    // Preserve function name
    Object.defineProperty(descriptor.value, 'name', {
      value: originalMethod.name,
      configurable: true,
    });

    return descriptor;
  };
}

/**
 * Parameter decorator that works in combination with ValidateParams.
 * This is primarily for TypeScript type checking and IDE support.
 * The actual validation is performed by ValidateParams method decorator.
 *
 * @deprecated Use ValidateParams method decorator instead
 * @param schema The Zod schema (for type checking only)
 * @param parameterName Parameter name (for type checking only)
 */
export function ZValidate<T>(_schema: z.ZodSchema<T>, _parameterName?: string) {
  return function (_target: any, propertyKey: string | symbol, _parameterIndex: number) {
    // This is a placeholder decorator that doesn't do anything
    // The actual validation should be done with ValidateParams
    console.warn(
      `ZValidate parameter decorator is deprecated. Use ValidateParams method decorator on ${String(propertyKey)} instead.`
    );
  };
}

/**
 * Convenience function to create a method decorator for a single parameter validation.
 *
 * @param parameterIndex The index of the parameter to validate (0-based)
 * @param schema The Zod schema to validate against
 * @param parameterName Optional parameter name for better error messages
 * @example
 * ```typescript
 * class UserService {
 *   @ValidateParam(0, z.string().email(), 'email')
 *   processUser(email: string, data: any) {
 *     // Method implementation
 *   }
 * }
 * ```
 */
export function ValidateParam(
  parameterIndex: number,
  schema: z.ZodSchema<any>,
  parameterName?: string
): MethodDecorator {
  return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    if (typeof originalMethod !== 'function') {
      return descriptor;
    }

    descriptor.value = function (this: any, ...args: any[]) {
      // Validate the specific parameter
      if (parameterIndex < args.length) {
        const paramValue = args[parameterIndex];

        try {
          const result = schema.safeParse(paramValue);
          if (!result.success) {
            throw new ZodValidationError(
              parameterIndex,
              parameterName,
              result.error
            );
          }
          // Replace the argument with the parsed/transformed value
          args[parameterIndex] = result.data;
        } catch (error) {
          if (error instanceof ZodValidationError) {
            throw error;
          } else if (error instanceof z.ZodError) {
            throw new ZodValidationError(
              parameterIndex,
              parameterName,
              error
            );
          } else {
            throw error;
          }
        }
      }

      return originalMethod.apply(this, args);
    };

    // Preserve function name
    Object.defineProperty(descriptor.value, 'name', {
      value: originalMethod.name,
      configurable: true,
    });

    return descriptor;
  };
}

/**
 * Options for configuring validation behavior
 */
export interface ZValidateOptions {
  /**
   * Custom parameter name for error messages
   */
  parameterName?: string;

  /**
   * Whether to throw on validation errors or return validation result
   * @default true
   */
  throwOnError?: boolean;
}

/**
 * Advanced parameter decorator with additional configuration options.
 * Provides the same validation as ZValidate but with more control over behavior.
 *
 * @param schema The Zod schema to validate the parameter against
 * @param options Configuration options for validation behavior
 * @example
 * ```typescript
 * class UserService {
 *   processUser(
 *     @ZValidateWithOptions(z.string().email(), {
 *       parameterName: 'userEmail',
 *       throwOnError: true
 *     }) email: string
 *   ) {
 *     // Method implementation
 *   }
 * }
 * ```
 */
export function ZValidateWithOptions<T>(
  schema: z.ZodSchema<T>,
  options: ZValidateOptions = {}
) {
  return ZValidate(schema, options.parameterName);
}