import asyncio
import base64
import json
from typing import Any, Awaitable, Callable, Dict

import aiofiles
from aiofiles import ospath
from aiofiles.tempfile import TemporaryDirectory

from .. import app_config, util
from ..broker import Broker, BrokerOptions
from ..logger import Logger
from ..request import Request
from ..response import Response

NAME = 'TranscribeWhisperLocal'
"""Name of the main class of the module.

Used by the App class
to obtain the correct class via getattr

"""


class TranscribeWhisperLocalOptions(BrokerOptions):
    """TranscribeWhisperLocal broker options.

    Attributes:
        path (str): path to whisper executable
        tmp_dir (str): temporary storage for audio files
        run_as_user (str): run the command as user

    """
    path: str
    tmp_dir: str
    run_as_user: str


class TranscribeWhisperLocal(Broker):
    """TranscribeWhisperLocal broker class, inherits Backend."""

    def __init__(self, send_req_cb: Callable[[Request],
                                             Awaitable[None]]) -> None:
        """Constructor for TranscribeWhisperLocal class.

        Args:
            send_req_cb (callable): callback that a broker can use to trigger
                sending another request. Set by the App class.

        Returns:
            None

        """
        self._logger = Logger(f'broker.{util.short_class_name(self)}')
        super().__init__(send_req_cb)

    async def start(self) -> None:
        """Starts the broker.

        Currently does nothing as there is no
        specific logic related to the broker startup. Reserved for
        future use and consistency with the parent class

        Args:
            None

        Returns
            None

        """
        pass

    async def process_request(self,
                              request: Request,
                              options: Dict[str, Any],
                              callback: Callable[[Request, Response],
                                                 Awaitable[None]]) -> None:
        """Processes the provided request and sends back responses.

        Uses the provided callback function

        Args:
            request (Request): Request message object
            options (Dict): broker options
            callback (Callable[[Request, Response], Awaitable[None]]):
                callback function to dispatch the responses to

        Returns
            None

        """
        whisper_options = TranscribeWhisperLocalOptions()
        util.apply_dict(options, whisper_options)

        response = Response()
        response.status = 'accepted'
        await asyncio.ensure_future(callback(request, response))

        data = base64.b64decode(request.data)

        response = Response()

        try:
            async with TemporaryDirectory(dir=whisper_options.tmp_dir) \
                    as tmp_dir:
                base_path = tmp_dir + '/audio.'
                audio_path = base_path + 'wav'
                json_path = base_path + 'json'

                async with aiofiles.open(audio_path, 'wb') as audio_file:
                    await audio_file.write(data)
                    await audio_file.close()

                stdout, stderr, code = await util.exec_command(
                        whisper_options.path,
                        f'--output_dir {tmp_dir} {audio_path}',
                        whisper_options.run_as_user)

                if code:
                    response.status = 'error'
                    response.reason = util.compact_str(
                            str(code)
                            + ' -- ' + bytes.decode(stderr)
                    )
                elif not await ospath.isfile(json_path):
                    response.status = 'error'
                    response.reason = 'no output'
                else:
                    async with aiofiles.open(json_path, 'r') as json_file:
                        output = json.loads(await json_file.read())
                    if not output:
                        response.status = 'error'
                        response.reason = 'empty output'
                    else:
                        response.status = 'done'
                        response.data = output
        finally:
            await asyncio.ensure_future(callback(request, response))
