import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PodcastEpisode, PodcastStatus } from './entities/podcast-episode.entity';
import { ConfigService } from '@nestjs/config';
// import { EdgeTTS } from 'node-edge-tts'; // Deprecated due to 403 errors
import * as fs from 'fs-extra';
import * as path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { KnowledgeGroup } from '../knowledge-group/knowledge-group.entity';
import { ChatService } from '../chat/chat.service';
import { I18nService } from '../i18n/i18n.service';

@Injectable()
export class PodcastService {
    private readonly logger = new Logger(PodcastService.name);
    private readonly outputDir: string;
    private readonly pythonPath = 'python'; // Or from config
    private readonly scriptPath = path.join(process.cwd(), 'text_to_speech.py');

    constructor(
        @InjectRepository(PodcastEpisode)
        private podcastRepository: Repository<PodcastEpisode>,
        @InjectRepository(KnowledgeGroup)
        private groupRepository: Repository<KnowledgeGroup>,
        private configService: ConfigService,
        private chatService: ChatService, // Reusing ChatService to generate script
        private i18nService: I18nService,
    ) {
        // this.tts = new EdgeTTS();
        this.outputDir = path.join(process.cwd(), 'uploads', 'podcasts');
        fs.ensureDirSync(this.outputDir);
    }

    async create(userId: string, createDto: any): Promise<PodcastEpisode> {
        this.logger.log(`Creating podcast with DTO: ${JSON.stringify(createDto)}`);
        if (!userId) {
            throw new Error(this.i18nService.getMessage('userIdRequired'));
        }

        const episode = this.podcastRepository.create({
            ...createDto,
            briefing: createDto.content || createDto.briefing, // Map content to briefing if needed
            user: { id: userId },
            group: createDto.groupId ? { id: createDto.groupId } : undefined,
            status: PodcastStatus.PENDING,
        }) as unknown as PodcastEpisode; // Restore cast to fix TS inference issue

        const saved = await this.podcastRepository.save(episode);

        // Start background processing
        this.processPodcast(saved.id, userId, createDto);

        return saved;
    }

    async findAll(userId: string, groupId?: string): Promise<PodcastEpisode[]> {
        const query = this.podcastRepository.createQueryBuilder('podcast')
            .where('podcast.userId = :userId', { userId })
            .orderBy('podcast.createdAt', 'DESC');

        if (groupId) {
            query.andWhere('podcast.groupId = :groupId', { groupId });
        }

        return query.getMany();
    }

    async findOne(userId: string, id: string): Promise<PodcastEpisode> {
        const episode = await this.podcastRepository.findOne({ where: { id, userId } });
        if (!episode) throw new NotFoundException(this.i18nService.formatMessage('podcastNotFound', { id }));
        return episode;
    }

    async delete(userId: string, id: string): Promise<void> {
        const episode = await this.findOne(userId, id);

        // Delete audio file if exists
        if (episode.audioUrl) {
            const filename = path.basename(episode.audioUrl);
            const filePath = path.join(this.outputDir, filename);
            if (fs.existsSync(filePath)) {
                fs.unlinkSync(filePath);
            }
        }

        await this.podcastRepository.remove(episode);
    }

    async processPodcast(episodeId: string, userId: string, dto: any) {
        try {
            this.logger.log(`Starting processing for podcast ${episodeId}`);
            await this.updateStatus(episodeId, PodcastStatus.PROCESSING);

            // 1. Gather Context
            let context = dto.content || '';
            if (dto.groupId) {
                // TODO: Fetch context from group (files) if content is empty
                // For now assuming content is passed or we just use what we have
            }

            // 2. Generate Script using ChatService (LLM)
            const fileIds = dto.fileId ? [dto.fileId] : undefined;
            const language = dto.language || 'zh';
            this.logger.log(`Generating script for language: ${language}`);
            const script = await this.generateScript(context, dto.topic || 'General Discussion', userId, language, dto.groupId, fileIds);
            await this.podcastRepository.update(episodeId, { transcript: script });

            // 3. Generate Audio using Edge TTS
            const audioFileName = `${episodeId}.mp3`;
            const audioFilePath = path.join(this.outputDir, audioFileName);

            await this.generateAudioInternal(script, audioFilePath, language);

            // 4. Update Episode
            await this.podcastRepository.update(episodeId, {
                status: PodcastStatus.COMPLETED,
                audioUrl: `/api/podcasts/audio/${audioFileName}`,
            });

            this.logger.log(`Podcast ${episodeId} completed (Language: ${language})`);

        } catch (error) {
            this.logger.error(`Failed to process podcast ${episodeId}`, error);
            await this.updateStatus(episodeId, PodcastStatus.FAILED);
        }
    }

    private async updateStatus(id: string, status: PodcastStatus) {
        await this.podcastRepository.update(id, { status });
    }

    async generateScript(context: string, topic: string, userId: string, language: string = 'zh', groupId?: string, fileIds?: string[]): Promise<any[]> {
        // ... (RAG context logic omitted for brevity, logic remains same)
        // If groupId or fileIds are provided, try to enhance context with RAG
        if ((groupId || (fileIds && fileIds.length > 0)) && (!context || context.length < 100)) {
            try {
                // tenantId is optional, we pass undefined here, groupId is string, fileIds is string[]
                const ragContext = await this.chatService.getContextForTopic(topic, userId, undefined, groupId, fileIds);
                if (ragContext) {
                    context = `Manual Context: ${context}\n\nSearch Results:\n${ragContext}`;
                }
            } catch (err) {
                this.logger.warn(`Failed to fetch RAG context for podcast: ${err.message}`);
            }
        }

        let targetLang = 'Chinese (Simplified)';
        if (language === 'en') targetLang = 'English';
        if (language === 'ja') targetLang = 'Japanese';

        const prompt = `
        You are an expert podcast producer. Create a podcast script about the following topic: "${topic}".
        
        Context information (use this to inform the discussion):
        ${context ? context.substring(0, 5000) : 'No specific context provided, use general knowledge.'}

        The podcast should be a dialogue between a Host and a Guest.
        - Host: Ask insightful questions and guide the conversation.
        - Guest: Provide expert answers and insights based on the context.
        - Tone: Professional yet conversational.
        - Length: Approximately 8-12 exchanges.
        - Language: ${targetLang}.
        
        IMPORTANT: The dialogue MUST be spoken in separate ${targetLang} sentences. Even if the context is valid in another language, translate the concepts and discuss them in ${targetLang}.

        Output the script as a valid JSON array of objects, where each object has "speaker" (Host/Guest) and "text" (the spoken content).
        Example:
        [
            // Example structure, ensure actual content is in ${targetLang}
            {"speaker": "Host", "text": "..."} 
        ]
        
        Do not include markdown formatting like \`\`\`json. Just the raw JSON.
        `;

        try {
            const rawContent = await this.chatService.generateSimpleChat(
                [{ role: 'user', content: prompt }],
                userId
            );

            // Clean up code blocks if present
            const jsonString = rawContent.replace(/```json/g, '').replace(/```/g, '').trim();

            try {
                return JSON.parse(jsonString);
            } catch (e) {
                this.logger.error('Failed to parse podcast script JSON:', rawContent);
                throw new Error(this.i18nService.getMessage('scriptGenerationFailed'));
            }
        } catch (error) {
            this.logger.error('Failed to generate script:', error);
            throw error;
        }
    }

    private async generateAudioInternal(script: any[], outputPath: string, language: string = 'zh') {
        const { spawn } = await import('child_process');
        const writeStream = fs.createWriteStream(outputPath);

        // Voice map
        const voices = {
            zh: { host: 'zh-CN-YunxiNeural', guest: 'zh-CN-XiaoxiaoNeural' },
            en: { host: 'en-US-AndrewNeural', guest: 'en-US-AvaNeural' },
            ja: { host: 'ja-JP-KeitaNeural', guest: 'ja-JP-NanamiNeural' }
        };

        const voiceConfig = voices[language] || voices['zh'];

        for (const line of script) {
            if (!line.text) continue;

            // Voice selection
            const voice = line.speaker === 'Host' ? voiceConfig.host : voiceConfig.guest;
            const tempPath = path.join(this.outputDir, `temp_${uuidv4()}.mp3`);

            try {
                await new Promise<void>((resolve, reject) => {
                    const process = spawn(this.pythonPath, [
                        this.scriptPath,
                        '--text', line.text,
                        '--voice', voice,
                        '--output', tempPath
                    ]);

                    let errorOutput = '';

                    process.stderr.on('data', (data) => {
                        errorOutput += data.toString();
                    });

                    process.on('close', (code) => {
                        if (code !== 0) {
                            reject(new Error(`Python script exited with code ${code}: ${errorOutput}`));
                        } else {
                            resolve();
                        }
                    });
                });

                if (fs.existsSync(tempPath)) {
                    const buffer = fs.readFileSync(tempPath);
                    writeStream.write(buffer);
                    fs.unlinkSync(tempPath);
                }

            } catch (e) {
                this.logger.error(`TTS Error for line: ${line.text}`, e);
            }
        }

        writeStream.end();
        await new Promise((resolve, reject) => {
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
        });
    }
}
