import g4f
from g4f import Provider
import json
import asyncio
import os
import random
from g4f.client import AsyncClient
import time
SYSTEM_PROMPT = '''
You are an expert at creating natural, authentic conversations between two friends discussing interesting topics.
Generate a realistic dialogue with synchronized visuals that feels like you're listening to two real people talk.

⚠️ CRITICAL JSON REQUIREMENTS ⚠️
- You MUST respond ONLY with valid JSON in the EXACT format specified below
- NO markdown formatting, NO code blocks, NO explanations outside JSON
- EVERY field must be included and properly formatted
- Use double quotes for all strings
- Follow the exact key names and structure
- The response must start with { and end with }

MANDATORY JSON STRUCTURE:
{
    "title": "Engaging Conversation Title",
    "description": "Brief description of the conversation",
    "voice_assignments": {
        "SPEAKER1": "nova",
        "SPEAKER2": "onyx"
    },
    "segments": [
        {
            "speaker": "SPEAKER1",
            "image_gen_prompt": "(Always In English Language) Detailed visual prompt for AI image generation",
            "text": "Natural spoken dialogue",
            "emotion": "happy",
            "duration_estimate": "12"
        }
    ]
}

NATURAL CONVERSATION GUIDELINES:

1. AUTHENTIC SPEECH PATTERNS:
   - Use contractions: "I'm", "don't", "we'll", "that's"
   - Include filler words: "um", "you know", "like", "well"
   - Add natural pauses: "So... what do you think about..."
   - Use incomplete sentences: "And then he just... wow."
   - Include interruptions and overlapping thoughts

2. REAL CONVERSATION FLOW:
   - People interrupt each other naturally
   - Thoughts jump around and circle back
   - Use "Oh wait", "Actually", "But here's the thing"
   - Reference previous parts: "Like you said earlier"
   - Build on each other's ideas organically

3. PERSONALITY DIFFERENCES:
   - SPEAKER1: More curious, asks follow-up questions, uses "Really?", "No way!", "That's crazy!"
   - SPEAKER2: More analytical, provides details, uses "Actually", "The thing is", "What I found interesting"
   - Both have distinct speaking styles and word choices

4. NATURAL REACTIONS:
   - Genuine surprise: "Wait, seriously?", "No kidding!"
   - Agreement: "Totally!", "Exactly!", "Right?", "I know, right?"
   - Disagreement: "Hmm, I'm not so sure", "Well, maybe, but..."
   - Thinking out loud: "Let me think...", "How do I put this..."

5. CONVERSATION STARTERS & TRANSITIONS:
   - "So I was reading about...", "You know what's weird?"
   - "That reminds me of...", "Speaking of which..."
   - "Wait, that's interesting because...", "Oh, and another thing..."
   - "But what I don't get is...", "Here's what blows my mind..."

6. EMOTIONAL AUTHENTICITY:
   - Show genuine excitement: "Dude, this is so cool!"
   - Express confusion: "I'm totally lost", "Wait, what?"
   - Share personal reactions: "That gives me chills", "I love that!"
   - Use conversational emphasis: "SO interesting", "absolutely fascinating"

7. REALISTIC DIALOGUE PATTERNS:
   - Start sentences with "And", "But", "So"
   - Use rhetorical questions: "Right?", "You know what I mean?"
   - Include self-corrections: "Well, not exactly, but..."
   - Add conversational asides: "which is funny because..."

8. TOPIC EXPLORATION STYLE:
   - Discover things together: "Oh wow, I never thought of that"
   - Share personal connections: "That happened to me once"
   - Question each other: "But why do you think that is?"
   - Build excitement together: "This is getting really interesting"

9. NATURAL CONVERSATION STRUCTURE:
   - Opening: Casual greeting and topic introduction
   - Exploration: Bouncing ideas back and forth
   - Moments of realization: "Oh! That makes sense now"
   - Tangents: Natural side conversations that connect back
   - Closing: "This was such a good talk", "We should talk about this more"

10. SPEECH CHARACTERISTICS:
    - Vary sentence lengths: short reactions + longer explanations
    - Use everyday language, not formal terms
    - Include conversational connectors: "plus", "also", "and then"
    - Show thinking process: "I mean", "like", "you know"

EMOTIONS (choose from): "happy", "excited", "curious", "thoughtful", "surprised", "neutral", "enthusiastic", "explanatory", "intrigued", "visionary", "amazed", "confused", "realizing"

DURATION GUIDELINES:
- Quick reactions: 3-6 seconds
- Normal responses: 8-15 seconds  
- Detailed explanations: 15-25 seconds
- Emotional moments: 5-10 seconds

IMAGE PROMPT GUIDELINES:
Create vivid, engaging visuals that match the conversation's energy:
- "Close-up of two friends having an animated discussion, warm lighting"
- "Animated visualization of [concept], dynamic and colorful"
- "Real-world example of [topic], photorealistic, engaging composition"
- Match the emotional tone of the dialogue

CONVERSATION EXAMPLE SNIPPETS:
"So I was thinking about this the other day, and it's like..."
"Wait, hold up - are you saying that...?"
"Dude, that's exactly what I was trying to figure out!"
"I know, right? It's so weird how..."
"But here's what I don't understand..."
"Oh my god, yes! And you know what else?"

CREATE 35-50 SEGMENTS with natural back-and-forth that feels like real friends talking about something they're genuinely interested in.

⚠️ REMEMBER ⚠️
- Make it sound like two real people, not a scripted interview
- Include natural speech patterns and reactions
- Let the conversation flow organically with tangents and discoveries
- Show genuine curiosity and excitement about the topic
- Use casual, everyday language throughout
'''
# Enhanced podcast discussion generation function
def generate_podcast_discussion(topic_prompt: str, language: str, max_retries: int = 3):
    """
    Generates a podcast discussion script with a retry mechanism.

    Args:
        topic_prompt: The prompt describing the podcast topic.
        language: The language for the podcast script.
        max_retries: The maximum number of times to retry on failure.

    Returns:
        A dictionary containing the parsed podcast data, or None if all retries fail.
    """
    for attempt in range(max_retries):
        try:
            print(f"--- Attempt {attempt + 1} of {max_retries} ---")
            client = g4f.Client()

            messages = [
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": f"Always Generate all text except image_gen_prompt in {language} language. The language is: {language}\n{topic_prompt}"}
            ]

            # Generate the podcast script
            response = client.chat.completions.create(model="gpt-5", messages=messages)
            raw_content = response.choices[0].message.content

            try:
                # The model might sometimes wrap the JSON in markdown backticks
                json_start = raw_content.find('{')
                json_end = raw_content.rfind('}') + 1
                if json_start == -1 or json_end == 0:
                    raise json.JSONDecodeError("No JSON object found in the response.", raw_content, 0)
                
                json_string = raw_content[json_start:json_end]
                podcast_data = json.loads(json_string)
            except json.JSONDecodeError as e:
                print(f"[Error] Response is not valid JSON: {e}")
                print("Raw response:", raw_content)
                # This will trigger the outer exception handler and cause a retry
                raise ValueError("Failed to decode JSON from response.") from e

            # --- Start Validation ---
            required_fields = ['title', 'voice_assignments', 'segments']
            for field in required_fields:
                if field not in podcast_data:
                    raise ValueError(f"Missing required top-level field: '{field}'")

            if not isinstance(podcast_data.get('segments'), list):
                raise ValueError("'segments' must be a list.")

            if len(podcast_data['segments']) < 30:
                print(f"[Warning] Only {len(podcast_data['segments'])} segments generated, which is less than the recommended 30-50.")

            for i, segment in enumerate(podcast_data['segments']):
                required_segment_fields = ['speaker', 'image_gen_prompt', 'text', 'emotion', 'duration_estimate']
                if not isinstance(segment, dict):
                    raise ValueError(f"Segment {i} is not a valid dictionary.")

                for field in required_segment_fields:
                    if field not in segment:
                        raise ValueError(f"Missing required field '{field}' in segment {i}")
                    if not isinstance(segment[field], str):
                        # Allow for integer duration, but cast to string for consistency
                        if field == 'duration_estimate' and isinstance(segment[field], int):
                            segment[field] = str(segment[field])
                        else:
                            raise ValueError(f"Field '{field}' must be a string in segment {i}, but got {type(segment[field])}")

                if segment['speaker'] not in ['SPEAKER1', 'SPEAKER2']:
                    raise ValueError(f"Invalid speaker '{segment['speaker']}' in segment {i}")

                try:
                    int(segment['duration_estimate'])
                except (ValueError, TypeError):
                    raise ValueError(f"'duration_estimate' must be a numeric string in segment {i}, but got '{segment['duration_estimate']}'")
            
            # --- End Validation ---

            print("[Success] Successfully generated and validated podcast data.")
            # print(json.dumps(podcast_data, indent=2)) # Optional: uncomment for detailed view
            return podcast_data

        except (ValueError, json.JSONDecodeError) as e:
            print(f"[Validation/JSON Error on Attempt {attempt + 1}] {e}")
            if attempt < max_retries - 1:
                print("Retrying in 2 seconds...")
                time.sleep(2)
            else:
                print("Maximum retries reached. Failed to get valid data.")
                return None
        
        except Exception as e:
            print(f"[General Error on Attempt {attempt + 1}] Failed to generate podcast discussion: {e}")
            if attempt < max_retries - 1:
                print("Retrying in 5 seconds...")
                time.sleep(5)
            else:
                print("Maximum retries reached. Aborting.")
                return None

    # This line is reached if the loop completes without a successful return
    return None


# Topic prompt banaune function
def create_topic_prompt(subject: str, context: str = "", specific_focus: str = ""):
    """
    Topic ko barema prompt banaune
    """
    if context and specific_focus:
        # Context ra specific focus dubai cha bhane
        return f"""Create an engaging podcast discussion about "{subject}"
        
Background context: {context}

Specific angle to focus on: {specific_focus}

Generate a complete podcast script with natural conversational dialogue between two hosts. Include detailed image generation prompts for each segment."""
    elif context:
        # Context matra cha bhane
        return f"""Create an engaging podcast discussion about "{subject}"
        
Background context: {context}

Generate a complete podcast script with natural conversational dialogue between two hosts. Include detailed image generation prompts for each segment."""
    else:
        # Simple subject matra diyeko cha bhane
        return f"""Create an engaging podcast discussion about "{subject}"
        
Generate a complete podcast script with natural conversational dialogue between two hosts. Include detailed image generation prompts for each segment."""

# Podcast ko segments haru ko lagi images generate garne
async def generate_images_for_segments(podcast_data, output_dir="images"):
    """
    Sabai segments ko lagi images generate garne function
    """
    if not os.path.exists(output_dir):
        # Output directory chaina bhane banaune
        os.makedirs(output_dir)
        
    try:
        # G4F ko async client banaune
        client = AsyncClient()
        
        # Images generate garne tasks haru create garne
        tasks = []
        for i, segment in enumerate(podcast_data["segments"]):
            prompt = segment.get("image_gen_prompt")
            if not prompt:
                continue
                
            # Image file ko naam
            file_name = f"{output_dir}/segment_{i+1:02d}.jpg"
            
            # Generate garne task add garne
            task = generate_image_for_segment(client, prompt, file_name, i)
            tasks.append(task)
            
        # Sabai tasks haru parallel ma run garne
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Results ma success ra failures count garne
        success_count = sum(1 for r in results if isinstance(r, bool) and r)
        failure_count = len(results) - success_count
        
        # Result ko summary return garne
        return {
            "total": len(tasks),
            "success": success_count,
            "failed": failure_count,
            "image_dir": output_dir
        }
    except Exception as e:
        # Kei error aayo bhane
        return {"error": str(e)}

# Single segment ko image generate garne
async def generate_image_for_segment(client, prompt, file_name, segment_index, max_retries=3, base_delay=1):
    """
    Euta segment ko image generate garne function with retry mechanism
    
    Args:
        client: G4F client
        prompt: Image generation prompt
        file_name: Output file path
        segment_index: Segment index for logging
        max_retries: Maximum number of retry attempts (default: 3)
        base_delay: Base delay between retries in seconds (default: 1)
        
    Returns:
        bool: True if successful, False otherwise
    """
    retries = 0
    last_error = None
    
    while retries <= max_retries:
        try:
            if retries > 0:
                # Log retry attempt
                print(f"Retry attempt {retries}/{max_retries} for segment {segment_index}")
            
            # G4F bata image generate garne
            response = await client.images.generate(
                prompt=prompt,
                model="dall-e-3",
                size="1024x1024",
                quality="standard"
            )
            
            # Response bata image URL paune
            if response and response.data and len(response.data) > 0:
                image_url = response.data[0].url
                
                # Image download garne with retry
                download_success = await download_image_with_retry(image_url, file_name, max_retries=2)
                
                if download_success:
                    if retries > 0:
                        print(f"Successfully generated image for segment {segment_index} after {retries} retries")
                    return True
                else:
                    last_error = "Failed to download image after successful generation"
            else:
                last_error = "Empty or invalid response from image generation API"
                
            # If we reach here, the attempt failed but didn't raise an exception
            retries += 1
            
            if retries <= max_retries:
                # Calculate delay with exponential backoff (1s, 2s, 4s, etc.)
                delay = base_delay * (2 ** (retries - 1))
                print(f"Waiting {delay}s before retry for segment {segment_index}")
                await asyncio.sleep(delay)
            
        except Exception as e:
            last_error = str(e)
            retries += 1
            
            if retries <= max_retries:
                # Calculate delay with exponential backoff and some jitter
                delay = base_delay * (2 ** (retries - 1)) * (0.75 + 0.5 * random.random())
                print(f"Error: {str(e)}. Waiting {delay:.2f}s before retry for segment {segment_index}")
                await asyncio.sleep(delay)
            else:
                print(f"Failed to generate image after {max_retries} retries. Last error: {str(e)}")
    
    # All retries failed
    print(f"All {max_retries} retries failed for segment {segment_index}. Last error: {last_error}")
    return False

# Image URL bata download garne function with retry
async def download_image_with_retry(url, file_path, max_retries=2, base_delay=0.5):
    """
    URL bata image download garera file ma save garne with retry mechanism
    """
    import aiohttp
    import aiofiles
    import random
    
    retries = 0
    
    while retries <= max_retries:
        try:
            async with aiohttp.ClientSession() as session:
                # URL bata image data fetch garne
                async with session.get(url) as response:
                    if response.status == 200:
                        # File ma save garne
                        async with aiofiles.open(file_path, 'wb') as f:
                            await f.write(await response.read())
                        return True
                    else:
                        retries += 1
                        if retries <= max_retries:
                            delay = base_delay * (2 ** (retries - 1)) * (0.75 + 0.5 * random.random())
                            print(f"Download failed with status {response.status}. Retrying in {delay:.2f}s")
                            await asyncio.sleep(delay)
                        else:
                            print(f"Download failed after {max_retries} retries. Status: {response.status}")
                            return False
        except Exception as e:
            retries += 1
            if retries <= max_retries:
                delay = base_delay * (2 ** (retries - 1))
                print(f"Download error: {str(e)}. Retrying in {delay:.2f}s")
                await asyncio.sleep(delay)
            else:
                print(f"Download failed after {max_retries} retries. Error: {str(e)}")
                return False
    
    return False

# Image URL bata download garne function
async def download_image(url, file_path):
    """
    URL bata image download garera file ma save garne
    """
    # Use the retry version instead
    return await download_image_with_retry(url, file_path)

# Podcast process garne main function
async def process_podcast_with_images(topic: str, language: str = "English"):
    """
    Topic bata podcast with images generate garne main function
    """
    try:
        # Topic prompt create garne
        topic_prompt = create_topic_prompt(topic)
        
        # Podcast data generate garne
        podcast_data, error = generate_podcast_discussion(topic_prompt, language)
        if error:
            return {"error": error}
            
        # Images generate garne folder ko path
        image_dir = os.path.join("podcast_output", "images", f"podcast_{topic.replace(' ', '_')[:10]}")
        
        # Images generate garne
        image_results = await generate_images_for_segments(podcast_data, image_dir)
        
        # Final result return garne
        return {
            "podcast_data": podcast_data,
            "image_results": image_results
        }
    except Exception as e:
        # Kei error aayo bhane
        return {"error": str(e)}