import { GoogleGenAI } from '@google/genai';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime') as any;
import sizeOf from 'image-size';
import {
  ImageGenerationOptions,
  ImageGenerationResult,
  ReferenceImage,
  GeneratedImageData,
  GeminiParams,
} from '../types/api';
import { StorageFactory } from './StorageFactory';
import { TTILogger, TTILogEntry } from './TTILogger';
import { NetworkErrorDetector } from '../utils/NetworkErrorDetector';
import { GeminiErrorDetector } from '../utils/GeminiErrorDetector';
import { ERROR_MESSAGES } from '../utils/constants/errors';

export class ImageGenService {
  private ai: GoogleGenAI;
  private primaryModel = 'gemini-2.5-flash-image';
  private static GEMINI_TIMEOUT_MS = 90_000; // 90 seconds

  constructor(apiKey: string) {
    if (!apiKey) {
      throw new Error('Gemini API key is required');
    }
    this.ai = new GoogleGenAI({ apiKey });
  }

  /**
   * Generate an image from text prompt with optional reference images
   * This method separates image generation from storage for clear error handling
   */
  async generateImage(options: ImageGenerationOptions): Promise<ImageGenerationResult> {
    const { prompt, imageId, referenceImages, aspectRatio, orgSlug, projectSlug, meta } = options;

    // Use default values if not provided
    const finalOrgSlug = orgSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
    const finalProjectSlug = projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main';
    const finalAspectRatio = aspectRatio || '16:9'; // Default to widescreen

    // Step 1: Generate image from Gemini AI
    let generatedData: GeneratedImageData;
    let geminiParams: GeminiParams;
    try {
      const aiResult = await this.generateImageWithAI(
        prompt,
        referenceImages,
        finalAspectRatio,
        finalOrgSlug,
        finalProjectSlug,
        meta,
      );
      generatedData = aiResult.generatedData;
      geminiParams = aiResult.geminiParams;
    } catch (error) {
      // Generation failed - return explicit error
      return {
        success: false,
        model: this.primaryModel,
        error: error instanceof Error ? error.message : 'Image generation failed',
        errorType: 'generation',
      };
    }

    // Step 2: Save generated image to storage
    // Path format: {orgSlug}/{projectSlug}/img/{imageId}
    try {
      const storageService = await StorageFactory.getInstance();
      // Original filename for metadata (e.g., "my-image.png")
      const originalFilename = `generated-image.${generatedData.fileExtension}`;

      const uploadResult = await storageService.uploadFile({
        orgSlug: finalOrgSlug,
        projectSlug: finalProjectSlug,
        imageId,
        buffer: generatedData.buffer,
        contentType: generatedData.mimeType,
        originalFilename,
      });

      if (uploadResult.success) {
        return {
          success: true,
          imageId: uploadResult.filename,
          filepath: uploadResult.path,
          url: uploadResult.url,
          size: uploadResult.size,
          model: this.primaryModel,
          geminiParams,
          generatedImageData: generatedData,
          ...(generatedData.description && {
            description: generatedData.description,
          }),
        };
      } else {
        // Storage failed but image was generated
        return {
          success: false,
          model: this.primaryModel,
          geminiParams,
          error: `Image generated successfully but storage failed: ${uploadResult.error || 'Unknown storage error'}`,
          errorType: 'storage',
          generatedImageData: generatedData,
          ...(generatedData.description && {
            description: generatedData.description,
          }),
        };
      }
    } catch (error) {
      // Storage exception - image was generated but couldn't be saved
      return {
        success: false,
        model: this.primaryModel,
        geminiParams,
        error: `Image generated successfully but storage failed: ${error instanceof Error ? error.message : 'Unknown storage error'}`,
        errorType: 'storage',
        generatedImageData: generatedData,
        ...(generatedData.description && {
          description: generatedData.description,
        }),
      };
    }
  }

  /**
   * Generate image using Gemini AI - isolated from storage logic
   * @throws Error if generation fails
   */
  private async generateImageWithAI(
    prompt: string,
    referenceImages: ReferenceImage[] | undefined,
    aspectRatio: string,
    orgSlug: string,
    projectSlug: string,
    meta?: { tags?: string[] },
  ): Promise<{
    generatedData: GeneratedImageData;
    geminiParams: GeminiParams;
  }> {
    const contentParts: any[] = [];

    // Add reference images if provided
    if (referenceImages && referenceImages.length > 0) {
      for (const refImage of referenceImages) {
        contentParts.push({
          inlineData: {
            mimeType: refImage.mimetype,
            data: refImage.buffer.toString('base64'),
          },
        });
      }
    }

    // Add text prompt
    contentParts.push({
      text: prompt,
    });

    // CRITICAL: Calculate exact values before SDK call
    // These exact objects will be passed to both SDK and logger
    const contents = [
      {
        role: 'user' as const,
        parts: contentParts,
      },
    ];

    const config = {
      responseModalities: ['IMAGE', 'TEXT'],
      imageConfig: {
        aspectRatio,
      },
    };

    // Capture Gemini SDK parameters for debugging
    const geminiParams: GeminiParams = {
      model: this.primaryModel,
      config,
      contentsStructure: {
        role: 'user',
        partsCount: contentParts.length,
        hasReferenceImages: !!(referenceImages && referenceImages.length > 0),
      },
    };

    // Log TTI request BEFORE SDK call - using exact same values
    const ttiLogger = TTILogger.getInstance();
    const logEntry: TTILogEntry = {
      timestamp: new Date().toISOString(),
      orgId: orgSlug,
      projectId: projectSlug,
      prompt,
      model: this.primaryModel,
      config,
      ...(meta && { meta }),
      ...(referenceImages &&
        referenceImages.length > 0 && {
          referenceImages: referenceImages.map((img) => ({
            mimetype: img.mimetype,
            size: img.buffer.length,
            originalname: img.originalname,
          })),
        }),
    };

    ttiLogger.log(logEntry);

    try {
      // Use the EXACT same config and contents objects calculated above
      // Wrap with timeout to prevent hanging requests
      const response = await this.withTimeout(
        this.ai.models.generateContent({
          model: this.primaryModel,
          config,
          contents,
        }),
        ImageGenService.GEMINI_TIMEOUT_MS,
        'Gemini image generation'
      );

      // Log response structure for debugging
      GeminiErrorDetector.logResponseStructure(response as any);

      // Check promptFeedback for blocked prompts FIRST
      if ((response as any).promptFeedback?.blockReason) {
        const errorResult = GeminiErrorDetector.analyzeResponse(response as any);
        console.error(
          `[ImageGenService] Prompt blocked:`,
          GeminiErrorDetector.formatForLogging(errorResult!)
        );
        throw new Error(errorResult!.message);
      }

      // Check if we have candidates
      if (!response.candidates || !response.candidates[0]) {
        const errorResult = GeminiErrorDetector.analyzeResponse(response as any);
        console.error(`[ImageGenService] No candidates in response`);
        throw new Error(errorResult?.message || 'No response candidates from Gemini AI');
      }

      const candidate = response.candidates[0];

      // Check finishReason for non-STOP completions
      if (candidate.finishReason && candidate.finishReason !== 'STOP') {
        const errorResult = GeminiErrorDetector.analyzeResponse(response as any);
        console.error(
          `[ImageGenService] Non-STOP finish reason:`,
          GeminiErrorDetector.formatForLogging(errorResult!)
        );
        throw new Error(errorResult!.message);
      }

      // Check content exists
      if (!candidate.content) {
        console.error(`[ImageGenService] No content in candidate`);
        throw new Error('No content in Gemini AI response');
      }

      const content = candidate.content;
      let generatedDescription: string | undefined;
      let imageData: { buffer: Buffer; mimeType: string } | null = null;

      // Extract image data and description from response
      for (const part of content.parts || []) {
        if (part.inlineData) {
          const buffer = Buffer.from(part.inlineData.data || '', 'base64');
          const mimeType = part.inlineData.mimeType || 'image/png';
          imageData = { buffer, mimeType };
        } else if (part.text) {
          generatedDescription = part.text;
        }
      }

      if (!imageData) {
        // Log what we got instead of image
        const partTypes = (content.parts || []).map((p: any) =>
          p.inlineData ? 'image' : p.text ? 'text' : 'other'
        );
        console.error(`[ImageGenService] No image data in response. Parts: [${partTypes.join(', ')}]`);
        throw new Error(
          `${ERROR_MESSAGES.GEMINI_NO_IMAGE}. Response contained: ${partTypes.join(', ') || 'nothing'}`
        );
      }

      const fileExtension = mime.getExtension(imageData.mimeType) || 'png';

      // Extract image dimensions from buffer
      let width = 1024; // Default fallback
      let height = 1024; // Default fallback
      try {
        const dimensions = sizeOf(imageData.buffer);
        if (dimensions.width && dimensions.height) {
          width = dimensions.width;
          height = dimensions.height;
        }
      } catch (error) {
        console.warn('Failed to extract image dimensions, using defaults:', error);
      }

      const generatedData: GeneratedImageData = {
        buffer: imageData.buffer,
        mimeType: imageData.mimeType,
        fileExtension,
        width,
        height,
        ...(generatedDescription && { description: generatedDescription }),
      };

      return {
        generatedData,
        geminiParams,
      };
    } catch (error) {
      // Check for rate limit (HTTP 429)
      const err = error as { status?: number; message?: string };
      if (err.status === 429) {
        const geminiError = GeminiErrorDetector.classifyApiError(error);
        console.error(
          `[ImageGenService] Rate limit:`,
          GeminiErrorDetector.formatForLogging(geminiError)
        );
        throw new Error(geminiError.message);
      }

      // Check for timeout
      if (error instanceof Error && error.message.includes('timed out')) {
        console.error(
          `[ImageGenService] Timeout after ${ImageGenService.GEMINI_TIMEOUT_MS}ms:`,
          error.message
        );
        throw new Error(
          `${ERROR_MESSAGES.GEMINI_TIMEOUT} after ${ImageGenService.GEMINI_TIMEOUT_MS / 1000} seconds`
        );
      }

      // Check for other API errors with status codes
      if (err.status) {
        const geminiError = GeminiErrorDetector.classifyApiError(error);
        console.error(
          `[ImageGenService] API error:`,
          GeminiErrorDetector.formatForLogging(geminiError)
        );
        throw new Error(geminiError.message);
      }

      // Enhanced error detection with network diagnostics
      if (error instanceof Error) {
        // Classify the error and check for network issues (only on failure)
        const errorAnalysis = await NetworkErrorDetector.classifyError(error, 'Gemini API');

        // Log the detailed error for debugging
        console.error(`[ImageGenService] ${NetworkErrorDetector.formatErrorForLogging(errorAnalysis)}`);

        // Throw user-friendly error message
        throw new Error(errorAnalysis.userMessage);
      }
      throw new Error('Gemini AI generation failed: Unknown error');
    }
  }

  /**
   * Wrap a promise with timeout
   */
  private async withTimeout<T>(
    promise: Promise<T>,
    timeoutMs: number,
    operationName: string
  ): Promise<T> {
    let timeoutId: NodeJS.Timeout;

    const timeoutPromise = new Promise<never>((_, reject) => {
      timeoutId = setTimeout(() => {
        reject(new Error(`${operationName} timed out after ${timeoutMs}ms`));
      }, timeoutMs);
    });

    try {
      const result = await Promise.race([promise, timeoutPromise]);
      clearTimeout(timeoutId!);
      return result;
    } catch (error) {
      clearTimeout(timeoutId!);
      throw error;
    }
  }

  static validateReferenceImages(files: Express.Multer.File[]): {
    valid: boolean;
    error?: string;
  } {
    if (files.length > 3) {
      return { valid: false, error: 'Maximum 3 reference images allowed' };
    }

    const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'];
    const maxSize = 5 * 1024 * 1024; // 5MB

    for (const file of files) {
      if (!allowedTypes.includes(file.mimetype)) {
        return {
          valid: false,
          error: `Unsupported file type: ${file.mimetype}. Allowed: PNG, JPEG, WebP`,
        };
      }

      if (file.size > maxSize) {
        return {
          valid: false,
          error: `File ${file.originalname} is too large. Maximum size: 5MB`,
        };
      }
    }

    return { valid: true };
  }

  static convertFilesToReferenceImages(files: Express.Multer.File[]): ReferenceImage[] {
    return files.map((file) => ({
      buffer: file.buffer,
      mimetype: file.mimetype,
      originalname: file.originalname,
    }));
  }
}
