"""
mechaclient.py - Pydle Client component for mechasqueak3

Copyright (c) 2018 The Fuel Rat Mischief,
All rights reserved.

Licensed under the BSD 3-Clause License.

See LICENSE.md

This module is built on top of the Pydle system.

"""
import functools

from typing import Optional

from loguru import logger
from uuid import uuid4

from pydle import Client

from .config.datamodel import ConfigRoot
from .packages.board import RatBoard
from .packages.commands import trigger
from .packages.fuelrats_api.v3.interface import ApiV300WSS
from .packages.permissions import require_permission, TECHRAT
from .packages.context.context import Context
from .packages.fact_manager.fact_manager import FactManager
from .packages.galaxy import Galaxy
from .packages.graceful_errors import graceful_errors
from .packages.utils import sanitize
from .features.message_history import MessageHistoryClient

from typing import Dict
import prometheus_client
from prometheus_async.aio import time as aio_time
import pendulum
ON_MESSAGE_TIME = prometheus_client.Histogram(
    name="on_message",
    namespace="client",
    documentation="time in on_message",
    unit="seconds"
)
TRACKED_MESSAGES = prometheus_client.Gauge(
    namespace="client",
    name="tracked_messages",
    documentation="number of last messages tracked"
)
IGNORED_MESSAGES = prometheus_client.Counter(
    name="ignored_messages",
    namespace="client",
    documentation="messages ignored by the client."
)
ERRORS = prometheus_client.Counter(
    name="errors",
    namespace="client",
    documentation="errors detected during message handling"
)


@require_permission(TECHRAT)
async def _on_invite(ctx: Context):
    await ctx.bot.join(ctx.channel)


class MechaClient(Client, MessageHistoryClient):
    """
    MechaSqueak v3_tests
    """

    __version__ = "3.0a"

    def __init__(self, *args, mecha_config: ConfigRoot, **kwargs):
        """
        Custom mechasqueak constructor

        Unused arguments are passed through to pydle's constructor

        Args:
            *args (list): arguments
            **kwargs (list): keyword arguments

        """
        self._api_handler: Optional[ApiV300WSS] = None
        self._fact_manager = None  # Instantiate Global Fact Manager
        self._last_user_message: Dict[str, str] = {}  # Holds last message from user, by irc nick
        self._rat_cache = None  # TODO: replace with ratcache once it exists
        self._rat_board = None  # Instantiate Rat Board
        self._config = mecha_config
        self._galaxy = None
        self._start_time = pendulum.now()
        self._on_invite = require_permission(TECHRAT)(functools.partial(self._on_invite))
        TRACKED_MESSAGES.set_function(lambda: len(self._last_user_message))
        super().__init__(*args, **kwargs)

    async def on_connect(self):
        """
        Called upon connection to the IRC server
        :return:
        """
        logger.debug(f"Connecting to channels...")
        # join a channel
        for channel in self._config.irc.channels:
            logger.debug(f"Configured channel {channel}")
            await self.join(channel)

        logger.debug("joined channels.")
        # call the super
        await super().on_connect()

    #
    # def on_join(self, channel, user):
    #     super().on_join(channel, user)
    async def on_invite(self, channel, by):
        logger.info(f"invited to channel {channel!r} by user {by!r}")
        # create context from message, tie the channel to the sender
        # (this ensures access-denys get sent to the right place)
        ctx = await Context.from_message(self, sender=by, channel=by, message=channel)
        logger.debug("invited to channel, context is {}", ctx)

        return await self._on_invite(ctx)

    async def _on_invite(self, ctx):
        await self.join(ctx.words[0])

    @aio_time(ON_MESSAGE_TIME)
    async def on_message(self, channel, user, message: str):
        """
        Triggered when a message is received
        :param channel: Channel the message arrived in
        :param user: user that triggered the message
        :param message: message body
        :return:
        """
        await super().on_message(channel, user, message)
        logger.debug(f"{channel}: <{user}> {message}")

        if user == self._config.irc.nickname:
            # don't do this and the bot can get int o an infinite
            # self-stimulated positive feedback loop.
            logger.debug(f"Ignored {message} (anti-loop)")
            IGNORED_MESSAGES.inc()
            return None
        # await command execution
        # sanitize input string headed to command executor
        sanitized_message = sanitize(message)
        logger.debug(f"Sanitized {sanitized_message}, Original: {message}")
        try:
            self._last_user_message[user.casefold()] = sanitized_message  # Store sanitized message
            ctx = await Context.from_message(
                self,
                channel=channel,
                sender=user,
                message=sanitized_message
            )

            if not ctx.words:
                logger.trace("ignoring empty message")
                IGNORED_MESSAGES.inc()
                return

            await trigger(ctx)

        # Disable pylint's complaint here, as a broad catch is exactly what we want.
        except Exception as ex:  # pylint: disable=broad-except
            ERRORS.inc()
            ex_uuid = uuid4()
            logger.exception(ex_uuid)
            error_message = graceful_errors.make_graceful(ex, ex_uuid)
            # and report it to the user
            await self.message(channel, error_message)

    # Vhost Handler
    async def on_raw_396(self, message):
        """
        Handle an IRC 396 message. This message is sent upon successful application of a host mask
        via usermode +x.
        """
        logger.info(f"{message.params[0]}@{message.params[1]} {message.params[2]}.")

    @property
    def rat_cache(self) -> object:
        """
        Rat Cache
        """
        return self._rat_cache

    @property
    def fact_manager(self) -> FactManager:
        """
        Fact Manager

        This is initialized in a lazy way to increase overall startup speed.
        """
        if not self._fact_manager:
            # Instantiate Global Fact Manager
            self._fact_manager = FactManager()
        return self._fact_manager

    @fact_manager.setter
    def fact_manager(self, manager: FactManager):
        """
        Fact Manager Setter.
        """
        if not isinstance(manager, FactManager):
            raise TypeError("fact_manager requires a FactManager.")

        logger.warning("Fact manager setter invoked!")
        self._fact_manager = manager

    @fact_manager.deleter
    def fact_manager(self):
        """
        Fact Manager Deleter
        """
        logger.warning("Fact Manager deleter invoked!")
        del self._fact_manager
        self._fact_manager = None

    @property
    def api_handler(self) -> ApiV300WSS:
        """
        API Handler property
        """
        if self._api_handler is None:
            self._api_handler = ApiV300WSS(config=self._config.api)
            self.board.api_handler = self._api_handler
        return self._api_handler

    @property
    def board(self) -> RatBoard:
        """
        Rat Board property

        """
        if self._rat_board is None:
            self._rat_board = RatBoard(
                api_handler=self._api_handler if self._api_handler else None
            )  # Create Rat Board Object
        return self._rat_board

    @board.setter
    def board(self, value):
        """
        Rat Board Setter
        """
        if not isinstance(value, RatBoard):
            raise TypeError("board property must be of type RatBoard.")

        logger.warning("Board Setter invoked!")
        self._rat_board = value

    @board.deleter
    def board(self):
        """
        Rat Board Deleter
        """
        logger.warning("Board Deleted!")
        del self._rat_board
        self._rat_board = None

    @property
    def galaxy(self) -> Galaxy:
        """
        Galaxy property
        """
        if not self._galaxy:
            self._galaxy = Galaxy()

        return self._galaxy

    @galaxy.setter
    def galaxy(self, value):
        """
        Galaxy setter
        """
        if not isinstance(value, Galaxy):
            raise TypeError("galaxy property must be of type Galaxy.")

        logger.warning("Galaxy Setter invoked!")
        self._galaxy = value

    @galaxy.deleter
    def galaxy(self):
        """
        Galaxy deleter
        """
        logger.warning("Galaxy deleted!")
        del self._galaxy
        self._galaxy = None

    @property
    def last_user_message(self) -> Dict[str, str]:
        return self._last_user_message

    @property
    def start_time(self) -> pendulum.DateTime:
        return self._start_time
