import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { TransitionType, AspectRatio } from '@prisma/client';
import { PrismaService } from '../../database/prisma.service';
import { VideoAiService } from '../video-ai/video-ai.service';
import { VideoQueueModule } from '../video-queue/video-queue.module';
import { VideoGenerationProducer } from '../video-queue/video-generation.producer';
import { XTwitterService } from '../x-twitter/x-twitter.service';
import { GeminiService } from '../gemini/gemini.service';
import { StorageService } from '../storage/storage.service';
import { ExtractorService } from '../extractor/extractor.service';
import { CreateProjectDto, UpdateProjectDto, CreateFromTweetDto, CreateFromYoutubeDto, CreateFromDocumentDto, CreateFromExtractedTextDto } from './dto/project.dto';
import sharp from 'sharp';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';

interface FindAllOptions {
  page: number;
  limit: number;
  status?: string;
}

@Injectable()
export class ProjectsService {
  private readonly logger = new Logger(ProjectsService.name);

  constructor(
    private readonly db: PrismaService,
    private readonly videoAiService: VideoAiService,
    private readonly videoGenerationProducer: VideoGenerationProducer,
    private readonly xTwitterService: XTwitterService,
    private readonly geminiService: GeminiService,
    private readonly storageService: StorageService,
    private readonly extractorService: ExtractorService,
  ) {}

  async create(userId: string, dto: CreateProjectDto) {
    const project = await this.db.project.create({
      data: {
        title: dto.title,
        description: dto.description,
        prompt: dto.prompt,
        language: dto.language || 'tr',
        aspectRatio: dto.aspectRatio || 'PORTRAIT_9_16',
        videoStyle: dto.videoStyle || 'CINEMATIC',
        targetDuration: dto.targetDuration || 60,
        seoKeywords: dto.seoKeywords || [],
        referenceUrl: dto.referenceUrl || null,
        status: 'DRAFT',
        userId,
      },
    });

    this.logger.log(`Proje oluşturuldu: ${project.id} — "${project.title}"`);
    return project;
  }

  async findAll(userId: string, options: FindAllOptions) {
    const { page, limit, status } = options;
    const skip = (page - 1) * limit;

    const where: Record<string, unknown> = {
      userId,
      deletedAt: null,
    };

    if (status) {
      where.status = status.toUpperCase();
    }

    const [projects, total] = await Promise.all([
      this.db.project.findMany({
        where,
        skip,
        take: limit,
        orderBy: { createdAt: 'desc' },
        select: {
          id: true,
          title: true,
          prompt: true,
          status: true,
          progress: true,
          thumbnailUrl: true,
          finalVideoUrl: true,
          language: true,
          videoStyle: true,
          aspectRatio: true,
          targetDuration: true,
          creditsUsed: true,
          createdAt: true,
          updatedAt: true,
          completedAt: true,
        },
      }),
      this.db.project.count({ where }),
    ]);

    return {
      data: projects,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  async findOne(userId: string, projectId: string) {
    const project = await this.db.project.findFirst({
      where: {
        id: projectId,
        userId,
        deletedAt: null,
      },
      include: {
        scenes: {
          orderBy: { order: 'asc' },
          include: {
            mediaAssets: true,
          },
        },
        mediaAssets: {
          where: { sceneId: null },
        },
        renderJobs: {
          orderBy: { createdAt: 'desc' },
          take: 5,
          include: {
            logs: {
              orderBy: { createdAt: 'desc' },
              take: 20,
            },
          },
        },
      },
    });

    if (!project) {
      throw new NotFoundException(`Proje bulunamadı: ${projectId}`);
    }

    return project;
  }

  async update(userId: string, projectId: string, dto: UpdateProjectDto) {
    const project = await this.findOne(userId, projectId);

    const activeStatuses = ['PENDING', 'GENERATING_MEDIA', 'RENDERING'];
    if (activeStatuses.includes(project.status)) {
      throw new BadRequestException(
        `İşlem devam ederken proje güncellenemez. Mevcut durum: ${project.status}`,
      );
    }

    const updated = await this.db.project.update({
      where: { id: projectId },
      data: {
        ...(dto.title && { title: dto.title }),
        ...(dto.description !== undefined && { description: dto.description }),
        ...(dto.prompt && { prompt: dto.prompt }),
        ...(dto.language && { language: dto.language }),
        ...(dto.aspectRatio && { aspectRatio: dto.aspectRatio }),
        ...(dto.videoStyle && { videoStyle: dto.videoStyle }),
        ...(dto.cinematicReference !== undefined && { cinematicReference: dto.cinematicReference }),
        ...(dto.targetDuration && { targetDuration: dto.targetDuration }),
      } as any,
    });

    this.logger.log(`Proje güncellendi: ${projectId}`);
    
    // Stil ya da görsel parametre değiştiyse ve senaryo/sahneler varsa otomatik rewrite başlat
    const styleChanged =
      (dto.videoStyle && dto.videoStyle !== project.videoStyle) ||
      (dto.cinematicReference !== undefined && dto.cinematicReference !== (project as any).cinematicReference) ||
      (dto.aspectRatio && dto.aspectRatio !== project.aspectRatio);

    if (styleChanged && project.scenes && project.scenes.length > 0) {
      this.logger.log(`Stil değişikliği tespit edildi (${projectId}), visual promptlar arka planda yenileniyor...`);
      this.rewriteVisualPromptsBackground(userId, projectId, updated).catch((err) => {
        this.logger.error(`Arka plan rewriteVisualPrompts hatası: ${err}`);
      });
    }

    return updated;
  }

  // Arka planda tüm promptları güncelleyen metod
  private async rewriteVisualPromptsBackground(userId: string, projectId: string, updatedProject: any) {
    const targetProject = await this.findOne(userId, projectId);
    if (!targetProject || !targetProject.scenes || targetProject.scenes.length === 0) return;

    try {
      // Sahnelerin visual prompt'larını yenile (ID'leri ve narration'ları gönderiyoruz)
      const mappedScenes = targetProject.scenes.map(s => ({
        id: s.id,
        order: s.order,
        narrationText: s.narrationText,
        visualPrompt: s.visualPrompt
      }));

      const rewritten = await this.videoAiService.rewriteAllVisualPrompts(
        mappedScenes,
        updatedProject.videoStyle,
        (updatedProject as any).cinematicReference,
        updatedProject.aspectRatio
      );

      // Veritabanına kaydet
      for (const newScene of rewritten) {
        await this.db.scene.update({
          where: { id: newScene.id },
          data: { visualPrompt: newScene.visualPrompt }
        });
      }
      this.logger.log(`Görsel promptlar ${projectId} için başarıyla yenilendi.`);
    } catch (err) {
      this.logger.error(`Visual promptları yenileme işlemi başarısız: ${err}`);
    }
  }

  async remove(userId: string, projectId: string) {
    await this.findOne(userId, projectId);
    await this.db.project.update({
      where: { id: projectId },
      data: { deletedAt: new Date() },
    });
    this.logger.log(`Proje silindi (soft): ${projectId}`);
  }

  async generateScript(userId: string, projectId: string) {
    const project = await this.findOne(userId, projectId);

    const activeStatuses = ['PENDING', 'GENERATING_MEDIA', 'RENDERING'];
    if (activeStatuses.includes(project.status)) {
      throw new BadRequestException(
        `İşlem devam ederken senaryo üretilemez. Mevcut durum: ${project.status}`,
      );
    }

    await this.db.project.update({
      where: { id: projectId },
      data: { status: 'GENERATING_SCRIPT' },
    });

    try {
      const scriptJson = await this.videoAiService.generateVideoScript({
        topic: project.prompt,
        targetDurationSeconds: project.targetDuration,
        language: project.language,
        videoStyle: project.videoStyle,
        cinematicReference: (project as any).cinematicReference || undefined,
        seoKeywords: (project as any).seoKeywords || [],
        referenceUrl: (project as any).referenceUrl || undefined,
      });

      // Mevcut sahneleri sil (yeniden üretim)
      await this.db.scene.deleteMany({ where: { projectId } });

      // Yeni sahneleri yaz
      const scenesData = scriptJson.scenes.map(
        (scene: {
          order: number;
          title?: string;
          narrationText: string;
          visualPrompt: string;
          subtitleText: string;
          durationSeconds: number;
          transitionType: string;
        }) => ({
          projectId,
          order: scene.order,
          title: scene.title || `Sahne ${scene.order}`,
          narrationText: scene.narrationText,
          visualPrompt: scene.visualPrompt,
          subtitleText: scene.subtitleText,
          duration: scene.durationSeconds,
          transitionType: this.mapTransitionType(scene.transitionType),
        }),
      );

      await this.db.scene.createMany({ data: scenesData });

      const updatedProject = await this.db.project.update({
        where: { id: projectId },
        data: {
          scriptJson: scriptJson as object,
          status: 'DRAFT',
          errorMessage: null,
          scriptVersion: { increment: 1 },
          // SEO & Social metadata (skill-enhanced)
          seoTitle: scriptJson.seo?.title || scriptJson.metadata.title,
          seoDescription: scriptJson.seo?.description || scriptJson.metadata.description,
          seoSchemaJson: scriptJson.seo?.schemaMarkup as object || null,
          socialContent: scriptJson.socialContent as object || null,
        },
        include: {
          scenes: { orderBy: { order: 'asc' } },
        },
      });

      this.logger.log(
        `Senaryo üretildi: ${projectId} — ${scriptJson.scenes.length} sahne`,
      );

      return updatedProject;
    } catch (error) {
      await this.db.project.update({
        where: { id: projectId },
        data: {
          status: 'DRAFT',
          errorMessage:
            error instanceof Error
              ? error.message
              : 'Senaryo üretimi sırasında bilinmeyen hata',
        },
      });
      throw error;
    }
  }

  async approveAndQueueGeneration(userId: string, projectId: string) {
    const project = await this.findOne(userId, projectId);

    if (!project.scriptJson) {
      throw new BadRequestException(
        'Onaylamadan önce bir senaryo üretilmelidir.',
      );
    }

    if (project.status !== 'DRAFT' && project.status !== 'FAILED') {
      throw new BadRequestException(
        `Proje yalnızca DRAFT veya FAILED durumunda onaylanabilir. Mevcut: ${project.status}`,
      );
    }

    await this.db.project.update({
      where: { id: projectId },
      data: { status: 'PENDING', progress: 0, errorMessage: null },
    });

    const renderJob = await this.db.renderJob.create({
      data: {
        projectId,
        status: 'QUEUED',
        queueName: 'video-generation',
        attemptNumber: 1,
        maxAttempts: 3,
      },
    });

    const bullJobId = await this.videoGenerationProducer.addVideoGenerationJob({
      projectId,
      renderJobId: renderJob.id,
      scriptJson: project.scriptJson,
      language: project.language,
      aspectRatio: project.aspectRatio,
      videoStyle: project.videoStyle,
      targetDuration: project.targetDuration,
      scenes: project.scenes.map((s) => {
        const thumbnail = s.mediaAssets?.find(m => m.type === 'THUMBNAIL');
        const imagePath = thumbnail && thumbnail.s3Key ? this.storageService.getAbsolutePath(thumbnail.s3Key) : undefined;
        return {
          id: s.id,
          order: s.order,
          narrationText: s.narrationText,
          visualPrompt: s.visualPrompt,
          subtitleText: s.subtitleText || s.narrationText,
          duration: s.duration,
          transitionType: s.transitionType,
          imagePath,
          ttsProvider: 'openai', // TODO: Make configurable from frontend or project settings
        };
      }),
    });

    await this.db.renderJob.update({
      where: { id: renderJob.id },
      data: { bullJobId },
    });

    this.logger.log(
      `Video üretimi kuyruğa eklendi — Project: ${projectId}, Job: ${bullJobId}`,
    );

    return {
      message: 'Video üretimi kuyruğa eklendi',
      projectId,
      renderJobId: renderJob.id,
      bullJobId,
      status: 'PENDING',
    };
  }

  private mapTransitionType(type: string): TransitionType {
    const validTypes = Object.values(TransitionType);
    const upper = type?.toUpperCase() as TransitionType;
    return validTypes.includes(upper) ? upper : TransitionType.CUT;
  }

  /**
   * X/Twitter tweet URL'sinden otomatik proje oluşturur ve senaryo üretir.
   *
   * Akış:
   * 1. FXTwitter API ile tweet verisini çek
   * 2. Tweet'i prompt'a dönüştür
   * 3. Proje oluştur (sourceType: X_TWEET)
   * 4. AI ile senaryo üret
   * 5. Sahneleri kaydet
   */
  async createFromTweet(userId: string, dto: CreateFromTweetDto) {
    this.logger.log(`Tweet'ten proje oluşturuluyor: ${dto.tweetUrl}`);

    // 1. Tweet verisini çek ve preview oluştur
    const preview = await this.xTwitterService.previewTweet(dto.tweetUrl);
    const { tweet } = preview;

    // 2. Proje başlığı: kullanıcı verdiyse onu kullan, yoksa önerilen
    const title = dto.title || preview.suggestedTitle;
    const prompt = preview.suggestedPrompt;

    // 3. Proje oluştur
    const project = await this.db.project.create({
      data: {
        title,
        description: `@${tweet.author.username} tweet'inden üretildi — ${tweet.metrics.likes} beğeni, ${tweet.metrics.views} görüntülenme`,
        prompt,
        language: dto.language || 'tr',
        aspectRatio: dto.aspectRatio || 'PORTRAIT_9_16',
        videoStyle: dto.videoStyle || 'CINEMATIC',
        targetDuration: dto.targetDuration || preview.estimatedDuration,
        status: 'GENERATING_SCRIPT',
        userId,
        sourceType: 'X_TWEET',
        sourceTweetData: {
          tweetId: tweet.id,
          tweetUrl: tweet.url,
          authorUsername: tweet.author.username,
          authorName: tweet.author.name,
          authorAvatar: tweet.author.avatarUrl,
          text: tweet.text,
          metrics: tweet.metrics,
          media: tweet.media,
          viralScore: preview.viralScore,
          contentType: preview.contentType,
          isThread: tweet.isThread,
          threadCount: tweet.threadTweets?.length || 1,
        } as object,
      },
    });

    this.logger.log(
      `Tweet proje oluşturuldu: ${project.id} — viral skor: ${preview.viralScore}/100`,
    );

    // 4. Senaryo üret
    try {
      const scriptJson = await this.videoAiService.generateVideoScript({
        topic: prompt,
        targetDurationSeconds: project.targetDuration,
        language: project.language,
        videoStyle: project.videoStyle,
        cinematicReference: (project as any).cinematicReference || undefined,
        seoKeywords: [],
        referenceUrl: dto.tweetUrl,
        sourceTweet: {
          authorUsername: tweet.author.username,
          text: tweet.text,
          media: tweet.media,
          metrics: tweet.metrics,
          isThread: tweet.isThread,
        },
      });

      // Sahneleri kaydet
      const scenesData = scriptJson.scenes.map(
        (scene: {
          order: number;
          title?: string;
          narrationText: string;
          visualPrompt: string;
          subtitleText: string;
          durationSeconds: number;
          transitionType: string;
        }) => ({
          projectId: project.id,
          order: scene.order,
          title: scene.title || `Sahne ${scene.order}`,
          narrationText: scene.narrationText,
          visualPrompt: scene.visualPrompt,
          subtitleText: scene.subtitleText,
          duration: scene.durationSeconds,
          transitionType: this.mapTransitionType(scene.transitionType),
        }),
      );

      await this.db.scene.createMany({ data: scenesData });

      const updatedProject = await this.db.project.update({
        where: { id: project.id },
        data: {
          scriptJson: scriptJson as object,
          status: 'DRAFT',
          errorMessage: null,
          scriptVersion: 1,
          seoTitle: scriptJson.seo?.title || scriptJson.metadata.title,
          seoDescription: scriptJson.seo?.description || scriptJson.metadata.description,
          seoSchemaJson: (scriptJson.seo?.schemaMarkup as object) || null,
          socialContent: (scriptJson.socialContent as object) || null,
        },
        include: {
          scenes: { orderBy: { order: 'asc' } },
        },
      });

      this.logger.log(
        `Tweet senaryo tamamlandı: ${project.id} — ${scriptJson.scenes.length} sahne`,
      );

      return {
        ...updatedProject,
        tweetPreview: {
          viralScore: preview.viralScore,
          contentType: preview.contentType,
          author: tweet.author,
          metrics: tweet.metrics,
        },
      };
    } catch (error) {
      await this.db.project.update({
        where: { id: project.id },
        data: {
          status: 'DRAFT',
          errorMessage:
            error instanceof Error
              ? error.message
              : 'Tweet senaryo üretimi sırasında hata',
        },
      });
      throw error;
    }
  }

  /**
   * YouTube URL'sinden proje oluşturur. Extractor servisi kullanılarak video transkripti çekilir.
   */
  async createFromYoutube(userId: string, dto: CreateFromYoutubeDto) {
    this.logger.log(`YouTube videosundan proje oluşturuluyor: ${dto.youtubeUrl}`);

    // 1. YouTube url'den MarkItDown yardımı ile metni çek
    const extractedText = await this.extractorService.extractFromUrl(dto.youtubeUrl);
    
    // 2. Proje başlığı veya varsayılan prompt'u oluştur
    const title = dto.title || 'YouTube Shorts Üretimi';
    const prompt = `Aşağıda dökümü (transcript) verilmiş YouTube videosundan en can alıcı 60 saniyelik bir Shorts videosu üret:\n\n${extractedText.substring(0, 15000)}`;

    // 3. Projeyi DRAFT vb. statülerle oluştur (Sonra AI için tetikleyebiliriz)
    const project = await this.db.project.create({
      data: {
        title,
        description: `YouTube video kaynağındaki veriden üretildi. URL: ${dto.youtubeUrl}`,
        prompt,
        language: dto.language || 'tr',
        aspectRatio: dto.aspectRatio || 'PORTRAIT_9_16',
        videoStyle: dto.videoStyle || 'CINEMATIC',
        targetDuration: dto.targetDuration || 60,
        status: 'GENERATING_SCRIPT',
        userId,
        sourceType: 'YOUTUBE',
        referenceUrl: dto.youtubeUrl,
      },
    });

    this.logger.log(`YouTube projesi oluşturuldu, senaryo üretiliyor: ${project.id}`);

    try {
      const scriptJson = await this.videoAiService.generateVideoScript({
        topic: prompt,
        targetDurationSeconds: project.targetDuration,
        language: project.language,
        videoStyle: project.videoStyle,
        cinematicReference: undefined,
        seoKeywords: [],
        referenceUrl: dto.youtubeUrl,
      });

      // Sahneleri kaydet
      const scenesData = scriptJson.scenes.map((scene: any) => ({
        projectId: project.id,
        order: scene.order,
        title: scene.title || `Sahne ${scene.order}`,
        narrationText: scene.narrationText,
        visualPrompt: scene.visualPrompt,
        subtitleText: scene.subtitleText,
        duration: scene.durationSeconds,
        transitionType: this.mapTransitionType(scene.transitionType),
      }));

      await this.db.scene.createMany({ data: scenesData });

      const updatedProject = await this.db.project.update({
        where: { id: project.id },
        data: {
          scriptJson: scriptJson as object,
          status: 'DRAFT',
          errorMessage: null,
          scriptVersion: 1,
        },
        include: {
          scenes: { orderBy: { order: 'asc' } },
        },
      });

      this.logger.log(`YouTube senaryo tamamlandı: ${project.id} — ${scriptJson.scenes.length} sahne`);
      return updatedProject;
    } catch (error) {
      await this.db.project.update({
        where: { id: project.id },
        data: {
          status: 'DRAFT',
          errorMessage: error instanceof Error ? error.message : 'YouTube senaryo üretimi sırasında hata',
        },
      });
      throw error;
    }
  }

  /**
   * PDF, Word vb. dokümandan metin çıkarır ve konu önerileri üretir.
   */
  async extractDocumentTopics(file: Express.Multer.File) {
    this.logger.log(`Belgeden konu önerileri çıkarılıyor: ${file.originalname}`);

    let tempFilePath: string | null = null;
    let extractedText = '';

    try {
      if (file.path) {
        tempFilePath = file.path;
      } else if (file.buffer) {
        tempFilePath = path.join(os.tmpdir(), `${Date.now()}-${file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_')}`);
        await fs.writeFile(tempFilePath, file.buffer);
      } else {
        throw new Error("Dosya içeriği okunamadı (Buffer veya Path yok).");
      }

      extractedText = await this.extractorService.extractFromFile(tempFilePath, file.originalname, file.mimetype);
    } finally {
      if (tempFilePath && !file.path) {
        await fs.unlink(tempFilePath).catch(e => this.logger.warn(`Temp dosya silinemedi: ${e.message}`));
      }
    }

    if (!extractedText || extractedText.trim().length === 0) {
      throw new BadRequestException("Belgeden okunabilir metin çıkarılamadı.");
    }

    // Kısa metinse doğrudan 1 konu öner (kendi başlığı gibi), uzunsa çoklu konu
    let topics: string[] = [];
    if (extractedText.length < 5000) {
       topics = [file.originalname.split('.')[0] || "Belge Özeti"];
    } else {
       topics = await this.videoAiService.suggestDocumentTopics(extractedText, 4);
    }

    return {
      text: extractedText,
      topics,
      originalFilename: file.originalname
    };
  }

  /**
   * PDF, Word vb. dokümandan proje oluşturur.
   */
  async createFromDocument(userId: string, file: Express.Multer.File, dto: CreateFromDocumentDto) {
    this.logger.log(`Belgeden proje oluşturuluyor: ${file.originalname}`);

    let tempFilePath: string | null = null;
    let extractedText = '';

    try {
      if (file.path) {
        tempFilePath = file.path;
      } else if (file.buffer) {
        tempFilePath = path.join(os.tmpdir(), `${Date.now()}-${file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_')}`);
        await fs.writeFile(tempFilePath, file.buffer);
      } else {
        throw new Error("Dosya içeriği okunamadı (Buffer veya Path yok).");
      }

      extractedText = await this.extractorService.extractFromFile(tempFilePath, file.originalname, file.mimetype);
    } finally {
      if (tempFilePath && !file.path) {
        await fs.unlink(tempFilePath).catch(e => this.logger.warn(`Temp dosya silinemedi: ${e.message}`));
      }
    }

    // Başlık ve prompt belirlenmesi
    const title = dto.title || `${file.originalname} Özeti`;
    const fullAiPrompt = `Aşağıda içeriği verilen dökümandan çarpıcı bir video senaryosu üret:\n\n${extractedText.substring(0, 15000)}`;
    const shortDbPrompt = `Belge üzerinden oluşturuldu: ${file.originalname}`;

    const project = await this.db.project.create({
      data: {
        title,
        description: `Belge üzerinden üretildi: ${file.originalname}`,
        prompt: shortDbPrompt,
        language: dto.language || 'tr',
        aspectRatio: dto.aspectRatio || 'PORTRAIT_9_16',
        videoStyle: dto.videoStyle || 'CINEMATIC',
        targetDuration: dto.targetDuration || 60,
        status: 'GENERATING_SCRIPT',
        userId,
        sourceType: 'DOCUMENT',
      },
    });

    try {
      const scriptJson = await this.videoAiService.generateVideoScript({
        topic: fullAiPrompt,
        targetDurationSeconds: project.targetDuration,
        language: project.language,
        videoStyle: project.videoStyle,
      });

      const scenesData = scriptJson.scenes.map((scene: any) => ({
        projectId: project.id,
        order: scene.order,
        title: scene.title || `Sahne ${scene.order}`,
        narrationText: scene.narrationText,
        visualPrompt: scene.visualPrompt,
        subtitleText: scene.subtitleText,
        duration: scene.durationSeconds,
        transitionType: this.mapTransitionType(scene.transitionType),
      }));

      await this.db.scene.createMany({ data: scenesData });

      const updatedProject = await this.db.project.update({
        where: { id: project.id },
        data: {
          scriptJson: scriptJson as object,
          status: 'DRAFT',
          errorMessage: null,
          scriptVersion: 1,
        },
        include: {
          scenes: { orderBy: { order: 'asc' } },
        },
      });

      return updatedProject;
    } catch (error) {
      await this.db.project.update({
        where: { id: project.id },
        data: {
          status: 'DRAFT',
          errorMessage: error instanceof Error ? error.message : 'Belge senaryo üretimi sırasında hata',
        },
      });
      throw error;
    }
  }

  /**
   * Çıkarılmış metin ve kullanıcının seçtiği bir "topic" üzerinden proje oluşturur.
   */
  async createFromExtractedText(userId: string, dto: CreateFromExtractedTextDto) {
    this.logger.log(`Metin ve konu üzerinden proje oluşturuluyor: ${dto.topic}`);

    const title = dto.topic;
    // Tam prompt metni (AI'a gönderilecek)
    const fullAiPrompt = `Aşağıda içeriği verilen metinden, özellikle "${dto.topic}" konusuna odaklanan çarpıcı bir video senaryosu üret:\n\n${dto.text.substring(0, 15000)}`;
    // Veritabanına kaydedilecek kısa prompt metni (VarChar 2000 limitine takılmaması için)
    const shortDbPrompt = `Belge/Metin üzerinden "${dto.topic}" konusu hedeflenerek oluşturuldu.`;

    const project = await this.db.project.create({
      data: {
        title,
        description: dto.originalFilename ? `Belgeden üretildi: ${dto.originalFilename} (Konu: ${dto.topic})` : `Metinden üretildi (Konu: ${dto.topic})`,
        prompt: shortDbPrompt,
        language: dto.language || 'tr',
        aspectRatio: (dto.aspectRatio as AspectRatio) || AspectRatio.PORTRAIT_9_16,
        videoStyle: dto.videoStyle || 'CINEMATIC',
        cinematicReference: dto.cinematicReference,
        targetDuration: dto.targetDuration || 60,
        status: 'GENERATING_SCRIPT',
        userId,
        sourceType: 'DOCUMENT',
      },
    });

    try {
      const scriptJson = await this.videoAiService.generateVideoScript({
        topic: fullAiPrompt,
        targetDurationSeconds: project.targetDuration,
        language: project.language,
        videoStyle: project.videoStyle,
        cinematicReference: project.cinematicReference ?? undefined,
      });

      const scenesData = scriptJson.scenes.map((scene: any) => ({
        projectId: project.id,
        order: scene.order,
        title: scene.title || `Sahne ${scene.order}`,
        narrationText: scene.narrationText,
        visualPrompt: scene.visualPrompt,
        subtitleText: scene.subtitleText,
        duration: scene.durationSeconds,
        transitionType: this.mapTransitionType(scene.transitionType),
      }));

      await this.db.scene.createMany({ data: scenesData });

      const updatedProject = await this.db.project.update({
        where: { id: project.id },
        data: {
          scriptJson: scriptJson as object,
          status: 'DRAFT',
          errorMessage: null,
          scriptVersion: 1,
        },
        include: {
          scenes: { orderBy: { order: 'asc' } },
        },
      });

      return updatedProject;
    } catch (error) {
      await this.db.project.update({
        where: { id: project.id },
        data: {
          status: 'DRAFT',
          errorMessage: error instanceof Error ? error.message : 'Konu bazlı senaryo üretimi sırasında hata',
        },
      });
      throw error;
    }
  }

  /**
   * Tekil sahne güncelleme — narrasyon, görsel prompt, altyazı veya süre.
   */
  async updateScene(
    userId: string,
    projectId: string,
    sceneId: string,
    data: { narrationText?: string; visualPrompt?: string; subtitleText?: string; duration?: number },
  ) {
    // Proje sahipliğini doğrula
    const project = await this.findOne(userId, projectId);

    const activeStatuses = ['PENDING', 'GENERATING_MEDIA', 'RENDERING'];
    if (activeStatuses.includes(project.status)) {
      throw new BadRequestException(
        `İşlem devam ederken sahneler düzenlenemez. Mevcut durum: ${project.status}`,
      );
    }

    // Sahnenin bu projeye ait olduğunu doğrula
    const scene = project.scenes.find((s) => s.id === sceneId);
    if (!scene) {
      throw new NotFoundException(`Sahne bulunamadı: ${sceneId}`);
    }

    const updated = await this.db.scene.update({
      where: { id: sceneId },
      data: {
        ...(data.narrationText !== undefined && { narrationText: data.narrationText }),
        ...(data.visualPrompt !== undefined && { visualPrompt: data.visualPrompt }),
        ...(data.subtitleText !== undefined && { subtitleText: data.subtitleText }),
        ...(data.duration !== undefined && { duration: data.duration }),
      },
      include: { mediaAssets: true },
    });

    this.logger.log(`Sahne güncellendi: ${sceneId} (proje: ${projectId})`);
    return updated;
  }

  /**
   * Tekil sahneyi AI ile yeniden üretir.
   * Mevcut sahnenin sıra numarası ve bağlamı korunur, sadece içerik yeniden üretilir.
   */
  async regenerateScene(userId: string, projectId: string, sceneId: string) {
    const project = await this.findOne(userId, projectId);

    const activeStatuses = ['PENDING', 'GENERATING_MEDIA', 'RENDERING'];
    if (activeStatuses.includes(project.status)) {
      throw new BadRequestException(
        `İşlem devam ederken sahneler yeniden üretilemez. Mevcut durum: ${project.status}`,
      );
    }

    const scene = project.scenes.find((s) => s.id === sceneId);
    if (!scene) {
      throw new NotFoundException(`Sahne bulunamadı: ${sceneId}`);
    }

    // Bağlam: Önceki ve sonraki sahne bilgisi
    const prevScene = project.scenes.find((s) => s.order === scene.order - 1);
    const nextScene = project.scenes.find((s) => s.order === scene.order + 1);

    // Stil DNA bilgilerini prompt'a dahil et
    const cinematicRef = (project as any).cinematicReference || '';
    const styleDNA = this.videoAiService.getStyleDNA(project.videoStyle, cinematicRef || undefined);

    const contextPrompt = `
Bir video senaryosunun ${scene.order}. sahnesini yeniden üret.
Proje konusu: ${project.prompt}
Proje dili: ${project.language}
Video stili: ${project.videoStyle}
${cinematicRef ? `Sinematik referans: ${cinematicRef}` : ''}
En-boy oranı: ${project.aspectRatio || 'PORTRAIT_9_16'}

═══ GÖRSEL STİL DNA (Bu kurallara KESİNLİKLE uy) ═══
Referans: ${styleDNA.reference}
Işık: ${styleDNA.lighting}
Lens: ${styleDNA.lens}
Renk: ${styleDNA.color}
Doku: ${styleDNA.texture}
═══════════════════════════════════════════

${prevScene ? `Önceki sahne narasyonu: "${prevScene.narrationText}"` : ''}
${prevScene ? `Önceki sahne visual prompt: "${prevScene.visualPrompt}"` : ''}
${nextScene ? `Sonraki sahne narasyonu: "${nextScene.narrationText}"` : ''}

ÖNEMLİ: visualPrompt İNGİLİZCE olmalı ve yukarıdaki Stil DNA bilgilerini yansıtmalı.
Narration ve subtitle ise ${project.language} dilinde olmalı.

Sadece bu tek sahneyi üret. JSON formatında:
{
  "narrationText": "...",
  "visualPrompt": "...",
  "subtitleText": "...",
  "durationSeconds": ${scene.duration}
}`;

    const result = await this.videoAiService.generateSingleScene(contextPrompt);

    // Stil DNA ile visual prompt'u zenginleştir (5-Layer Architecture™)
    const isHookScene = scene.order === 1;
    const enrichedVisualPrompt = this.videoAiService.enrichSingleVisualPrompt(
      result.visualPrompt,
      project.videoStyle,
      cinematicRef || undefined,
      project.aspectRatio,
      isHookScene,
    );

    const updated = await this.db.scene.update({
      where: { id: sceneId },
      data: {
        narrationText: result.narrationText,
        visualPrompt: enrichedVisualPrompt,
        subtitleText: result.subtitleText || result.narrationText,
        duration: result.durationSeconds || scene.duration,
      },
      include: { mediaAssets: true },
    });

    this.logger.log(`Sahne yeniden üretildi: ${sceneId} (proje: ${projectId}) — Stil: ${project.videoStyle}${cinematicRef ? ', Ref: ' + cinematicRef : ''}`);
    return updated;
  }

  async generateSceneImage(userId: string, projectId: string, sceneId: string, customPrompt?: string) {
    const project = await this.findOne(userId, projectId);
    const scene = project.scenes.find((s) => s.id === sceneId);
    if (!scene) {
      throw new NotFoundException('Sahne bulunamadı');
    }

    if (customPrompt && customPrompt !== scene.visualPrompt) {
      // First update the prompt
      await this.updateScene(userId, projectId, sceneId, { visualPrompt: customPrompt });
      scene.visualPrompt = customPrompt;
    }

    this.logger.log(`Sahne görseli üretiliyor: ${sceneId} (proje: ${projectId})`);

    const aspectRatioMap: Record<string, '16:9' | '9:16' | '1:1'> = {
      'PORTRAIT_9_16': '9:16',
      'LANDSCAPE_16_9': '16:9',
      'SQUARE_1_1': '1:1',
    };
    const mappedRatio = aspectRatioMap[project.aspectRatio] || '9:16';

    // Görüntüyü üret — stil + sinematik referans bilgisini dahil et
    const cinematicRef = (project as any).cinematicReference || '';
    const styleLabel = cinematicRef
      ? `Style: ${project.videoStyle}, Cinematic reference: ${cinematicRef}`
      : `Style: ${project.videoStyle}`;
    let imageResult = await this.geminiService.generateImage(
      `${scene.visualPrompt}. ${styleLabel}`,
      mappedRatio,
    );
    
    if (!imageResult) {
      this.logger.warn(`⚠️ Orijinal prompt ile görsel üretilemedi. Güvenli fallback deneniyor...`);
      const safePrompt = `A cinematic, highly detailed abstract visualization matching the mood of: ${project.videoStyle}. Ensure professional quality, 8k resolution. Do not include specific people, recognizable faces, or real-world public figures.`;
      imageResult = await this.geminiService.generateImage(safePrompt, mappedRatio);
    }

    if (!imageResult) {
      throw new BadRequestException('Görsel üretilemedi, güvenlik filtreleri veya servis hatası nedeniyle işlem başarısız oldu.');
    }

    // Storage'a kaydet
    const key = this.storageService.getSceneImageKey(projectId, scene.order);
    await this.storageService.upload(key, imageResult.buffer, imageResult.mimeType);
    const url = this.storageService.getPublicUrl(key);

    // MediaRecord oluştur veya güncelle
    let mediaAsset = scene.mediaAssets.find(m => m.type === 'THUMBNAIL');
    let mediaId = mediaAsset?.id;
    if (!mediaId) {
      const media = await this.db.mediaAsset.create({
        data: {
          projectId,
          type: 'THUMBNAIL',
          url: url,
          fileName: `scene-${scene.order}.png`,
          sizeBytes: imageResult.buffer.length,
          mimeType: imageResult.mimeType,
          sceneId: scene.id,
        },
      });
      mediaId = media.id;
    } else {
      await this.db.mediaAsset.update({
        where: { id: mediaId },
        data: { url: url, sizeBytes: imageResult.buffer.length },
      });
    }

    const updated = await this.db.scene.findUnique({
      where: { id: sceneId },
      include: { mediaAssets: true },
    });
    return updated;
  }

  async upscaleSceneImage(userId: string, projectId: string, sceneId: string) {
    const project = await this.findOne(userId, projectId);
    const scene = project.scenes.find((s) => s.id === sceneId);
    if (!scene) {
      throw new NotFoundException('Sahne bulunamadı');
    }

    const mediaAsset = scene.mediaAssets.find(m => m.type === 'THUMBNAIL');
    let mediaId = mediaAsset?.id;
    if (!mediaId) {
      throw new BadRequestException('Bu sahne için upscaled edilecek görsel bulunamadı.');
    }

    const media = await this.db.mediaAsset.findUnique({ where: { id: mediaId } });
    if (!media) throw new NotFoundException('Medya kaydı bulunamadı');

    this.logger.log(`Sahne görseli upscaled ediliyor (Sharp ile simülasyon): ${sceneId}`);

    const key = this.storageService.getSceneImageKey(projectId, scene.order);
    const absPath = this.storageService.getAbsolutePath(key);

    try {
      const buffer = await fs.readFile(absPath);

      // Determine dimensions based on ratio
      let width = 3840;
      let height = 2160;
      if (project.aspectRatio === 'PORTRAIT_9_16') {
        width = 2160;
        height = 3840;
      } else if (project.aspectRatio === 'SQUARE_1_1') {
        width = 2160;
        height = 2160;
      }

      // Upscale logic simulated natively locally using Sharp
      const upscaledBuffer = await sharp(buffer)
        .resize(width, height, { fit: 'cover', kernel: 'lanczos3' })
        .toFormat('png')
        .toBuffer();

      // Overwrite the image
      await this.storageService.upload(key, upscaledBuffer, 'image/png');

      await this.db.mediaAsset.update({
        where: { id: mediaId },
        data: { sizeBytes: upscaledBuffer.length },
      });

      return await this.db.scene.findUnique({
        where: { id: sceneId },
        include: { mediaAssets: true },
      });
    } catch (error) {
      this.logger.error(`Upscale sırasında hata: ${error.message}`);
      throw new BadRequestException('Görsel bulunamadı veya işlenemedi.');
    }
  }
}
