import asyncio
import aiohttp
import os
import logging

from dataclasses import dataclass
from typing import AsyncGenerator

from dailyai.pipeline.aggregators import (
    GatedAggregator,
    LLMFullResponseAggregator,
    ParallelPipeline,
    SentenceAggregator,
)
from dailyai.pipeline.frames import (
    Frame,
    TextFrame,
    EndFrame,
    ImageFrame,
    LLMMessagesFrame,
    LLMResponseStartFrame,
)
from dailyai.pipeline.frame_processor import FrameProcessor

from dailyai.pipeline.pipeline import Pipeline
from dailyai.transports.daily_transport import DailyTransport
from dailyai.services.open_ai_services import OpenAILLMService
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
from dailyai.services.fal_ai_services import FalImageGenService

from runner import configure

from dotenv import load_dotenv
load_dotenv(override=True)

logging.basicConfig(format=f"%(levelno)s %(asctime)s %(message)s")
logger = logging.getLogger("dailyai")
logger.setLevel(logging.DEBUG)


@dataclass
class MonthFrame(Frame):
    month: str


class MonthPrepender(FrameProcessor):
    def __init__(self):
        self.most_recent_month = "Placeholder, month frame not yet received"
        self.prepend_to_next_text_frame = False

    async def process_frame(self, frame: Frame) -> AsyncGenerator[Frame, None]:
        if isinstance(frame, MonthFrame):
            self.most_recent_month = frame.month
        elif self.prepend_to_next_text_frame and isinstance(frame, TextFrame):
            yield TextFrame(f"{self.most_recent_month}: {frame.text}")
            self.prepend_to_next_text_frame = False
        elif isinstance(frame, LLMResponseStartFrame):
            self.prepend_to_next_text_frame = True
            yield frame
        else:
            yield frame


async def main(room_url):
    async with aiohttp.ClientSession() as session:
        transport = DailyTransport(
            room_url,
            None,
            "Month Narration Bot",
            mic_enabled=True,
            camera_enabled=True,
            mic_sample_rate=16000,
            camera_width=1024,
            camera_height=1024,
        )

        tts = ElevenLabsTTSService(
            aiohttp_session=session,
            api_key=os.getenv("ELEVENLABS_API_KEY"),
            voice_id=os.getenv("ELEVENLABS_VOICE_ID"),
        )

        llm = OpenAILLMService(
            api_key=os.getenv("OPENAI_API_KEY"),
            model="gpt-4-turbo-preview")

        imagegen = FalImageGenService(
            params=FalImageGenService.InputParams(
                image_size="square_hd"
            ),
            aiohttp_session=session,
            key=os.getenv("FAL_KEY"),
        )

        gated_aggregator = GatedAggregator(
            gate_open_fn=lambda frame: isinstance(
                frame, ImageFrame), gate_close_fn=lambda frame: isinstance(
                frame, LLMResponseStartFrame), start_open=False, )

        sentence_aggregator = SentenceAggregator()
        month_prepender = MonthPrepender()
        llm_full_response_aggregator = LLMFullResponseAggregator()

        pipeline = Pipeline(
            processors=[
                llm,
                sentence_aggregator,
                ParallelPipeline(
                    [[month_prepender, tts], [llm_full_response_aggregator, imagegen]]
                ),
                gated_aggregator,
            ],
        )

        frames = []
        for month in [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
        ]:
            messages = [
                {
                    "role": "system",
                    "content": f"Describe a nature photograph suitable for use in a calendar, for the month of {month}. Include only the image description with no preamble. Limit the description to one sentence, please.",
                }
            ]
            frames.append(MonthFrame(month))
            frames.append(LLMMessagesFrame(messages))

        frames.append(EndFrame())
        await pipeline.queue_frames(frames)

        await transport.run(pipeline, override_pipeline_source_queue=False)


if __name__ == "__main__":
    (url, token) = configure()
    asyncio.run(main(url))
