import { v4 as uuidv4 } from 'uuid';
import { resolve } from 'path';
import { writeFile } from 'fs/promises';
import { createModuleLogger } from '../../utils/logger.js';
import { config } from '../../utils/config.js';
import { queue } from '../queue-system/index.js';
import { dataStore } from '../data-store/index.js';
import { buildSubjectDescription, buildCameraSettings } from '../character-engine/prompt-builder.js';
import { KIEClient, createKIEClient } from './kie-client.js';
import { TemplateManager } from './templates.js';
import type { CharacterDNA, MasterJSONPrompt } from '../../types/character.js';
import type { SceneTemplate } from '../../types/api.js';

const log = createModuleLogger('image-engine');

const MAX_RETRIES = 3;

export interface GenerateImageOptions {
  model?: string;
  aspectRatio?: string;
}

export class ImageEngine {
  private kieClient: KIEClient;
  private templateManager: TemplateManager;
  /** Tracks retry attempts per original generation request */
  private retryMap: Map<string, { attempts: number; characterId: string; templateId: string; options?: GenerateImageOptions }> = new Map();

  constructor(kieClient?: KIEClient, templateManager?: TemplateManager) {
    this.kieClient = kieClient || createKIEClient();
    this.templateManager = templateManager || new TemplateManager();
  }

  /**
   * Combines Scene Template + Character DNA + Master JSON Prompt into a final prompt string.
   */
  buildFinalPrompt(dna: CharacterDNA, masterPrompt: MasterJSONPrompt, template: SceneTemplate): string {
    const subject = buildSubjectDescription(dna);
    const camera = buildCameraSettings(dna);
    const clothing = masterPrompt.clothing;
    const lighting = template.environment.lighting || masterPrompt.lighting;
    const aesthetic = masterPrompt.aesthetic;

    // Fill template placeholders
    let prompt = template.promptTemplate;
    prompt = prompt.replace(/\{subject\}/g, subject);
    prompt = prompt.replace(/\{camera\}/g, camera);
    prompt = prompt.replace(/\{clothing\}/g, clothing);
    prompt = prompt.replace(/\{lighting\}/g, lighting);
    prompt = prompt.replace(/\{aesthetic\}/g, aesthetic);
    prompt = prompt.replace(/\{environment\}/g, masterPrompt.environment);

    return prompt;
  }

  /**
   * Queues an image generation task. Returns the queue task ID.
   */
  async generateImage(
    characterId: string,
    templateId: string,
    options?: GenerateImageOptions,
  ): Promise<string> {
    const character = await dataStore.getCharacter(characterId);
    if (!character) throw new Error(`Character not found: ${characterId}`);

    const masterPrompt = await dataStore.getMasterPrompt(characterId);
    if (!masterPrompt) throw new Error(`Master prompt not found for character: ${characterId}`);

    const template = await dataStore.getTemplate(templateId);
    if (!template) throw new Error(`Template not found: ${templateId}`);

    const finalPrompt = this.buildFinalPrompt(character, masterPrompt, template);
    const model = options?.model || 'flux-2';
    const aspectRatio = options?.aspectRatio || template.defaultAspectRatio || '1:1';

    const queueItem = await queue.add('image_generation', characterId, {
      prompt: finalPrompt,
      negativePrompt: masterPrompt.negative_prompt,
      model,
      aspectRatio,
      templateId,
      webhookUrl: config.webhookBaseUrl ? `${config.webhookBaseUrl}/api/v1/webhooks/kie` : undefined,
      isReferenceImage: false,
    });

    // Track for retry
    this.retryMap.set(queueItem.id, { attempts: 0, characterId, templateId, options });

    log.info({ taskId: queueItem.id, characterId, templateId, model }, 'Image generation queued');
    return queueItem.id;
  }

  /**
   * Processes a completed image result from webhook callback.
   * Downloads and saves the image to the character's generated-images directory.
   */
  async handleWebhookResult(taskId: string, resultUrl: string): Promise<void> {
    log.info({ taskId, resultUrl }, 'Processing webhook result');

    const queueItem = queue.getById(taskId);
    if (!queueItem) {
      log.warn({ taskId }, 'Queue item not found for webhook result');
      return;
    }

    const characterId = queueItem.characterId;
    const imageId = uuidv4();
    const imagesDir = dataStore.getGeneratedImagesDir(characterId);
    const imagePath = resolve(imagesDir, `${imageId}.jpg`);

    try {
      const response = await fetch(resultUrl);
      if (!response.ok) throw new Error(`Failed to download image: ${response.status}`);

      const buffer = Buffer.from(await response.arrayBuffer());
      await writeFile(imagePath, buffer);

      await queue.updateStatus(taskId, 'completed');
      log.info({ taskId, imageId, characterId }, 'Image saved successfully');
    } catch (err: any) {
      log.error({ taskId, err: err.message }, 'Failed to process webhook result');
      await this.handleFailure(taskId, err.message);
    }
  }

  /**
   * Handles a failed image generation task. Auto-retries up to MAX_RETRIES.
   */
  async handleFailure(taskId: string, error: string): Promise<void> {
    const retryInfo = this.retryMap.get(taskId);

    if (retryInfo && retryInfo.attempts < MAX_RETRIES) {
      retryInfo.attempts++;
      log.info({ taskId, attempt: retryInfo.attempts, maxRetries: MAX_RETRIES }, 'Retrying image generation');

      try {
        const newTaskId = await this.generateImage(
          retryInfo.characterId,
          retryInfo.templateId,
          retryInfo.options,
        );
        // Transfer retry tracking to new task
        const newRetryInfo = this.retryMap.get(newTaskId);
        if (newRetryInfo) {
          newRetryInfo.attempts = retryInfo.attempts;
        }
      } catch (retryErr: any) {
        log.error({ taskId, err: retryErr.message }, 'Retry failed');
      }

      this.retryMap.delete(taskId);
    } else {
      log.error({ taskId, error }, 'Image generation failed after max retries');
      await queue.updateStatus(taskId, 'failed', error);
      this.retryMap.delete(taskId);
    }
  }
}
