// src/loaders/prismaLoader.ts
import { ILoader, PipelineContext, DatabaseTargetConfig } from '../core/interfaces';
import { PrismaClient } from '@prisma/client';

interface PrismaTargetConfig extends DatabaseTargetConfig {
    type: 'postgresql' | 'mysql';
    connection: PrismaClient;
    model: string;
    operation?: 'create' | 'upsert' | 'update'; // Default to 'create'
    upsertWhereField?: string; // Field to use for the 'where' in upsert (e.g. 'id' or 'email')
    // Add batch transaction options if needed
}

// MARK: - PrismaLoader
export class PrismaLoader<TInput extends object> implements ILoader<TInput> {
    private config: PrismaTargetConfig;

    constructor(config: PrismaTargetConfig) {
        if (!config.connection || !config.model) {
            throw new Error('PrismaLoader requires "connection" (PrismaClient instance) and "model" in config.');
        }
        this.config = config;
        this.config.operation = config.operation || 'create';
        if (this.config.operation === 'upsert' && !this.config.upsertWhereField) {
            throw new Error("PrismaLoader with 'upsert' operation requires 'upsertWhereField' config.");
        }
    }

    // MARK: - loadBatch
    async loadBatch(batch: TInput[], context: PipelineContext): Promise<void> {
        if (batch.length === 0) return; // Nothing to load

        const modelDelegate = (this.config.connection as any)[this.config.model];
        if (!modelDelegate) {
             throw new Error(`Prisma model "${this.config.model}" not found on the provided client.`);
        }

        context.logger.debug(`Loading batch of ${batch.length} items to Prisma model ${this.config.model} using operation ${this.config.operation}`);

        try {
            // Prisma's createMany is often faster for simple inserts
            if (this.config.operation === 'create') {
                const result = await modelDelegate.createMany({
                    data: batch,
                    skipDuplicates: true, // Optional: ignore errors if a record already exists (depends on constraints)
                });
                context.logger.info(`Prisma createMany inserted ${result.count} records.`);
            } else if (this.config.operation === 'upsert') {
                // Upsert needs to be done one by one or in a transaction
                // Using transaction for potentially better performance
                const upsertPromises = batch.map(item => {
                    const whereValue = (item as any)[this.config.upsertWhereField!];
                    if(whereValue === undefined || whereValue === null) {
                        context.logger.warn({ item, field: this.config.upsertWhereField }, `Skipping upsert: where field '${this.config.upsertWhereField}' is missing or null.`);
                        return Promise.resolve(); // Skip this item
                    }
                    return modelDelegate.upsert({
                         where: { [this.config.upsertWhereField!]: whereValue },
                         update: item, // Update with the full item
                         create: item, // Create with the full item
                     });
                });
                // Execute upserts sequentially for now, batch transaction is better
                // await this.config.connection.$transaction(upsertPromises); // Requires Prisma preview feature or check version
                for (const promise of upsertPromises) { // Sequential execution
                    await promise;
                }
                 context.logger.info(`Prisma upsert processed ${batch.length} records.`);

            } else {
                 // TODO: Implement 'update' if needed (likely requires a unique key in each item)
                 throw new Error(`PrismaLoader operation '${this.config.operation}' not yet implemented.`);
            }
        } catch (error) {
             context.logger.error({ err: error }, `Error loading batch to Prisma model ${this.config.model}`);
             // Handle batch errors - potentially log failed items?
             throw error; // Rethrow to be caught by retry logic
        }
    }
}