""".. module:: YourModuleName
   :synopsis: A brief description of what this module does.

.. moduleauthor:: Oscar Bahamonde <oscar.bahamonde.dev@gmail.com>"""

from io import BytesIO
from random import choice
from typing import Any, List, Optional

from boto3 import Session
from pydantic import BaseModel, Field  # pylint: disable=no-name-in-module
from typing_extensions import Literal

from ..lib import *

polly = Session().client("polly", region_name="us-east-1")
comprehend = Session().client("comprehend", region_name="us-east-1")
translate = Session().client("translate", region_name="us-east-1")


class Voice(BaseModel):
    VoiceId: str = Field(...)
    LanguageCode: str = Field(...)
    Engine: Literal["standard", "neural"] = Field(...)

   
    @classmethod
    @async_io
    def load(cls) -> List["Voice"]:
        voices = []
        response = polly.describe_voices()
        for voice in response.get("Voices", []):
            voice_obj = cls(
                VoiceId=voice["Id"], # type: ignore
                LanguageCode=voice["LanguageCode"], # type: ignore
                Engine=voice["SupportedEngines"][0], # type: ignore
            )
            voices.append(voice_obj) # type: ignore
        return voices # type: ignore

    @classmethod
    @async_io
    def detect_language(cls, text: str):
        response =comprehend.detect_dominant_language(Text=text)["Languages"]
        return choice(response)["LanguageCode"] # type: ignore

    @classmethod
    async def assign(cls, text: str)->List["Voice"]:
        voices = await cls.load()
        lang = await cls.detect_language(text)
        return [voice for voice in voices if voice.LanguageCode.startswith(lang)]


class Polly(BaseModel):
    Engine: Literal["standard", "neural"] = Field(
        default="standard",
        description="Specifies the engine for Amazon Polly to use when processing input text for speech synthesis.",
    )
    LanguageCode: str = Field(
        default="en-US",
        description="Optional language code for the Synthesize Speech request.",
    )
    LexiconNames: Optional[List[str]] = Field(
        default=None,
        description="List of one or more pronunciation lexicon names you want the service to apply during synthesis.",
    )
    OutputFormat: Literal["json", "mp3", "ogg_vorbis", "pcm"] = Field(
        default="mp3",
        description="The format in which the returned output will be encoded.",
    )
    SampleRate: str = Field(
        default="22050", description="The audio frequency specified in Hz."
    )
    SpeechMarkTypes: Optional[
        List[Literal["sentence", "ssml", "viseme", "word"]]
    ] = Field(
        default=None,
        description="The type of speech marks returned for the input text.",
    )
    Text: str = Field(..., description="Input text to synthesize.")
    TextType: Literal["ssml", "text"] = Field(
        default="text",
        description="Specifies whether the input text is plain text or SSML.",
    )
    VoiceId: str = Field(
        default="Mia", description="Voice ID to use for the synthesis."
    )

    @classmethod
    def load(cls, text: str, language: str = "en-US"):
        return cls(Text=text, LanguageCode=language)

    def synthesize(self):
        return polly.synthesize_speech(**self.dict(exclude_none=True))

    @async_io
    def get_audio(self):
        byte_stream = BytesIO()
        with self.synthesize()["AudioStream"] as stream:
            byte_stream.write(stream.read())
        byte_stream.seek(0)
        return byte_stream

    async def stream_audio(self):
        with self.synthesize()["AudioStream"] as stream:
            while chunk := stream.read(1024):
                yield chunk


class SentimentScore(BaseModel):
    Mixed: float = Field(
        default=None,
        description="The level of confidence that Amazon Comprehend has in the accuracy of its detection of the MIXED sentiment.",
    )
    Negative: float = Field(
        default=None,
        description="The level of confidence that Amazon Comprehend has in the accuracy of its detection of the NEGATIVE sentiment.",
    )
    Neutral: float = Field(
        default=None,
        description="The level of confidence that Amazon Comprehend has in the accuracy of its detection of the NEUTRAL sentiment.",
    )
    Positive: float = Field(
        default=None,
        description="The level of confidence that Amazon Comprehend has in the accuracy of its detection of the POSITIVE sentiment.",
    )
    Sentiment: Literal["POSITIVE", "NEGATIVE", "NEUTRAL", "MIXED"] = Field(
        default=None,
        description="The inferred sentiment that Amazon Comprehend has the highest level of confidence in.",
    )


class Comprehend(BaseModel):
    Text: str = Field(
        ...,
        description="A UTF-8 text string. Each string must contain fewer that 5,000 bytes of UTF-8 encoded characters.",
    )

   
    
    @async_io
    def run(self,**kwargs:Any):
        response = comprehend.detect_sentiment(Text=self.Text, LanguageCode=self.detect_language(self.Text))
        return SentimentScore(
            Sentiment=response["Sentiment"], **response["SentimentScore"]
        )

    @classmethod
    def detect_language(cls, text: str):
        return comprehend.detect_dominant_language(Text=text)["Languages"][
            0
        ]["LanguageCode"] # type: ignore


class Translate(BaseModel):
    SourceLanguageCode: str = Field(
        default="auto",
        description="The language code for the language of the source text. The language must be a language supported by Amazon Translate.",
    )
    TargetLanguageCode: str = Field(
        default="en",
        description="The language code requested for the language of the target text. The language must be a language supported by Amazon Translate.",
    )
    Text: str = Field(
        ...,
        description="The text to translate. The text string can be a maximum of 5,000 bytes long. Depending on your character set, this may be fewer than 5,000 characters.",
    )

    @classmethod
    def load(
        cls,
        text: str,
        source: str = "auto",
        target: str = "en",
    ):
        return cls(Text=text, SourceLanguageCode=source, TargetLanguageCode=target)

    @async_io
    def run(self, **kwargs:Any):
        response = translate.translate_text(**self.dict(exclude_none=True))
        return response["TranslatedText"]
