// src/transformers/validationTransformer.ts
import { z, ZodSchema, ZodError } from 'zod';
import { ITransformer, PipelineContext, PipelineStepConfig } from '../core/interfaces';
import { ComponentError, ConfigurationError } from '../core/errors';

// Define how schema is provided in config
interface ValidationConfig extends PipelineStepConfig {
    transformer: 'validator'; // Registered name
    schema: object | string; // Can be inline Zod schema object (as JSON) or path to a .js/.ts file exporting a schema
    // What to do on validation failure: 'error', 'filter', 'dlq' (dlq needs pipeline context)
    onFailure?: 'error' | 'filter' | 'log'; // Default to 'filter'? 'error'? Let's default to 'error'
}

export class ValidationTransformer<TInput, TOutput = TInput> implements ITransformer<TInput, TOutput> {
    private schema: ZodSchema<TOutput> | null = null;
    private config: ValidationConfig;
    private schemaSource: object | string;
    private onFailure: 'error' | 'filter' | 'log';

    constructor(config: ValidationConfig) {
        if (!config.schema) {
            throw new ConfigurationError(`ValidationTransformer requires 'schema' in step config.`);
        }
        this.config = config;
        this.schemaSource = config.schema;
        this.onFailure = config.onFailure || 'error'; // Default to throwing error
    }

    // Lazy load/compile the schema
    private async loadSchema(context: PipelineContext): Promise<ZodSchema<TOutput>> {
        if (this.schema) return this.schema;

        context.logger.debug({ schemaSource: this.schemaSource }, `Loading/Compiling Zod schema for ValidationTransformer.`);
        try {
            let schemaDefinition: any;
            if (typeof this.schemaSource === 'string') {
                // Load from file path (relative to CWD or absolute)
                // SECURITY: Be very careful loading code dynamically. Ensure paths are trusted.
                const schemaPath = path.resolve(process.cwd(), this.schemaSource);
                context.logger.info(`Loading Zod schema from file: ${schemaPath}`);
                const module = await import(schemaPath);
                // Assume schema is default export or named export 'schema'
                schemaDefinition = module.default || module.schema;
                if (!schemaDefinition) {
                     throw new ConfigurationError(`No Zod schema found in export from ${schemaPath}`);
                }
            } else {
                // Assume inline object definition needs to be parsed by Zod
                 // This is less common/useful - Zod schemas are usually defined in code.
                 // A better approach might be to expect a registered schema by ID.
                 // For simplicity, let's assume if it's an object, it *is* the Zod schema instance
                 // This means config needs to pass the actual Zod object, which is hard via JSON/YAML.
                 // Recommendation: Use file path method or register schemas.
                 // Let's stick to file path or throw error for inline object for now.
                throw new ConfigurationError(`Inline object schema definitions are not directly supported. Please provide a file path.`);
                 // schemaDefinition = this.schemaSource; // If we assumed it's already a Zod object
            }

            // Check if it's a Zod schema instance
            if (schemaDefinition instanceof ZodSchema) {
                 this.schema = schemaDefinition as ZodSchema<TOutput>;
                 context.logger.debug(`Zod schema loaded successfully.`);
                 return this.schema;
            } else {
                 throw new ConfigurationError(`Loaded schema source is not a valid Zod schema instance.`);
            }

        } catch (error: any) {
             context.logger.error({ err: error, schemaSource: this.schemaSource }, `Failed to load or compile Zod schema.`);
             if (error instanceof ConfigurationError || error instanceof ComponentError) throw error;
             throw new ComponentError('Failed to load Zod schema', 'ValidationTransformer', error);
        }
    }

    async transform(data: TInput, context: PipelineContext): Promise<TOutput> {
         const zodSchema = await this.loadSchema(context);

        try {
            // Parse (validates and returns typed output, potentially transforming)
            const validatedData = await zodSchema.parseAsync(data);
            return validatedData;
        } catch (error: any) {
             if (error instanceof ZodError) {
                 context.logger.warn({ zodErrors: error.errors, item: data }, `Validation failed for item.`);
                 switch (this.onFailure) {
                     case 'filter':
                         // Return null to signal filtering (pipeline run logic needs to handle null)
                         // Note: ITransformer interface expects TOutput, not null.
                         // This requires adjusting the pipeline logic or the interface.
                         // Alternative: Throw specific error caught by pipeline's error handler
                         throw new ValidationError('Item failed validation (strategy: filter)', error.errors, data);
                     case 'log':
                          // Logged above, just return original data (or null/undefined?)
                          // Returning original data might violate TOutput type. Risky.
                          // Let's throw specific error like 'filter'.
                           throw new ValidationError('Item failed validation (strategy: log)', error.errors, data);
                     case 'error':
                     default:
                          throw new ValidationError('Item failed validation (strategy: error)', error.errors, data); // Rethrow validation error
                 }
             } else {
                 // Unexpected error during parsing
                 context.logger.error({ err: error, item: data }, `Unexpected error during validation.`);
                 throw new ComponentError('Unexpected validation error', 'ValidationTransformer', error);
             }
        }
    }
}

// Custom error class for validation failures
export class ValidationError extends ComponentError {
    public readonly validationErrors: z.ZodIssue[];
    public readonly originalItem: any;

    constructor(message: string, errors: z.ZodIssue[], item: any) {
         super(message, 'ValidationTransformer');
         this.validationErrors = errors;
         this.originalItem = item; // Include item for DLQ/logging
         this.name = 'ValidationError';
    }
}