import asyncio
import copy
from typing import Any, Dict, Optional

from . import app_config, util
from .backend import Backend
from .base import Base
from .broker import Broker
from .logger import Logger
from .request import Request
from .response import Response


class TaskConfig:
    """Base class for task config attributes.

    Attributes:
        name (str): task name
        backend (str): backend name
        backend_options (Dict): backend options
        broker str: broker name
        broker_options (Dict): broker options
        follow (FollowTask): optional next task(s)

    """
    name: str
    backend: str
    backend_options: Dict[str, Any]
    broker: str
    broker_options: Dict[str, Any]


class Task(Base):
    """Task class, inherits Base.

    Attributes:
        _name (str): task name
        _config (TaskConfig): task config
        _backend (Backend): backend object
        _broker (Broker): broker object
        _running (bool): true if the task is started, otherwise false

    """
    _name: str
    _config: TaskConfig
    _backend: Backend
    _broker: Broker
    _running: bool

    def __init__(self,
                 config: TaskConfig, backend: Backend, broker: Broker) -> None:
        """Constructor for Task class.

        Args:
            config (TaskConfig): task config
            backend (Backend): task backend
            broker (Broker): task broker

        Returns:
            None

        """
        self._logger = Logger(f'{util.short_class_name(self)}.{config.name}')
        self._name = config.name
        self._config = config
        self._backend = backend
        self._broker = broker
        super().__init__()

    def name(self) -> str:
        """Fetches task's name.

        Args:
            None

        Returns:
            str: task name

        """
        return self._name

    def config(self) -> TaskConfig:
        """Fetches task's config.

        Args:
            None

        Returns:
            TaskConfig: task config

        """
        return copy.deepcopy(self._config)

    def backend(self) -> Backend:
        """Fetches task's backend.

        Args:
            None

        Returns:
            Backend: task backend

        """
        return self._backend

    def broker(self) -> Broker:
        """Fetches task's broker.

        Args:
            None

        Returns:
            Broker: task broker

        """
        return self._broker

    def is_running(self) -> bool:
        """Returns true if the task is running.

        Args:
            None

        Returns:
            bool: true if running

        """
        return self._running

    async def on_request(self, request: Request) -> None:
        """Called when a new request arrives from clients.

        Callback method that is used by backends

        Args:
            request (Request): request message

        Returns:
            None

        """
        if not self.is_running():
            return

        if not request.uuid:
            response = Response()
            response.status = 'rejected'
            response.reason = 'missing uuid'
            self.log.debug(
                f'response: {util.compact_str(str(vars(response)))}'
            )
            await asyncio.ensure_future(
                self.backend().send_response(request, response)
            )
            return

        if request.task != self.name():
            return

        asyncio.create_task(
            self.broker().process_request(request,
                                          self._config.broker_options,
                                          self.on_response)
        )

    async def on_response(self, request: Request, response: Response) -> None:
        """Called when a response is initiated by a broker.

        Callback method that is used by brokers

        Args:
            request (Request): original request message
            response (Request): response message

        Returns:
            None

        """
        response.ref = request.uuid
        response.task = request.task
        response.src = app_config.ngcp.node_name
        response.dst = request.src
        response.ref = request.uuid

        if response.status == 'accepted':
            dst_nodes_in_accepted: bool = False
            parse_opt = request.options.get('dst_nodes_in_accepted', False)
            parse_opt_type = type(parse_opt)
            if parse_opt_type is str:
                dst_nodes_in_accepted = util.str_to_bool(str(parse_opt))
            else:
                dst_nodes_in_accepted = bool(parse_opt)
            if dst_nodes_in_accepted:
                dst = request.dst
                response.data = list(await util.get_dst_nodes(dst))

        self.log.debug(
            f'response: {util.compact_str(str(vars(response)))}'
        )
        await self.backend().send_response(request, response)

    async def start(self) -> None:
        """Starts the task and attaches to the backend.

        Args:
            None

        Returns
            None

        """
        await self.backend().attach(self.name(),
                                    self.config().backend_options,
                                    self.on_request)
        self._running = True
        self.log.info(f'started task name={self._name}')

    async def stop(self) -> None:
        """Stops the task.

        Args:
            None

        Returns
            None

        """
        self._running = False
        await self.backend().detach(self.name())
        self.log.info(f'stopped task name={self._name}')
