"""
Module defining all the "info wins", ie the bar which is on top of the
info buffer in normal tabs
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import Optional, Dict, TYPE_CHECKING, Any, Callable, Protocol

import logging

from slixmpp import JID, InvalidJID

from poezio.config import config

from poezio.windows.base_wins import Win
from poezio.ui.funcs import truncate_nick
from poezio.theming import get_theme, to_curses_attr, TextStyle
from poezio.colors import ccg_text_to_color
from poezio.contact import Contact, Resource

if TYPE_CHECKING:
    from poezio.user import User
    from poezio.tabs import MucTab
    from poezio.windows import TextWin

log = logging.getLogger(__name__)


AdditionalCb = Callable[[str], str]


class HasPos(Protocol):
    """Protocol for a class that has a pos attribute or property"""
    @property
    def pos(self) -> int:
        ...


class InfoWin(Win):
    """
    Base class for all the *InfoWin, used in various tabs. For example
    MucInfoWin, etc. Provides some useful methods.
    """

    __slots__ = ()

    def print_scroll_position(self, window: 'HasPos') -> None:
        """
        Print, like in Weechat, a -MORE(n)- where n
        is the number of available lines to scroll
        down
        """
        if window.pos > 0:
            plus = ' -MORE(%s)-' % window.pos
            self.addstr(plus,
                        to_curses_attr(get_theme().COLOR_SCROLLABLE_NUMBER))


class XMLInfoWin(InfoWin):
    """
    Info about the latest xml filter used and the state of the buffer.
    """

    __slots__ = ()

    def refresh(self, window: 'TextWin', filter_t: str = '', filter: str = '') -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        bar = to_curses_attr(get_theme().COLOR_INFORMATION_BAR)
        if not filter_t:
            self.addstr('[No filter]', bar)
        else:
            info = '[%s] %s' % (filter_t, filter)
            self.addstr(info, bar)
        self.print_scroll_position(window)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()


class PrivateInfoWin(InfoWin):
    """
    The line above the information window, displaying information
    about the MUC user we are talking to
    """

    __slots__ = ()

    def refresh(self, name: str, window: 'TextWin', chatstate: str | None,
                information: Mapping[str, AdditionalCb]) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        self.write_room_name(name)
        self.print_scroll_position(window)
        self.write_chatstate(chatstate)
        self.write_additional_information(information, name)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()

    def write_additional_information(self, information: Mapping[str, AdditionalCb], jid: JID | str) -> None:
        """
        Write all information added by plugins by getting the
        value returned by the callbacks.
        """
        for plugin in information.values():
            self.addstr(plugin(str(jid)),
                        to_curses_attr(get_theme().COLOR_INFORMATION_BAR))

    def write_room_name(self, name: str) -> None:
        # TODO: autocolour this too, but we need more info about the occupant
        # (whether we know its real jid) and the room (whether it is
        # anonymous) to provide correct colouring.
        try:
            jid = JID(name)
        except InvalidJID:
            jid = JID('')
        room_name, nick = jid.bare, jid.resource
        theme = get_theme()
        self.addstr(nick, to_curses_attr(theme.COLOR_PRIVATE_NAME))
        txt = ' from room %s' % room_name
        self.addstr(txt, to_curses_attr(theme.COLOR_INFORMATION_BAR))

    def write_chatstate(self, state: str | None) -> None:
        if state:
            self.addstr(' %s' % (state, ),
                        to_curses_attr(get_theme().COLOR_INFORMATION_BAR))


class MucListInfoWin(InfoWin):
    """
    The live above the information window, displaying informatios
    about the muc server being listed
    """

    __slots__ = ('message')

    def __init__(self, message: str = '') -> None:
        InfoWin.__init__(self)
        self.message = message

    def refresh(self, name: str | None = None, window: HasPos | None = None) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        theme = get_theme()
        if name:
            self.addstr(name,
                        to_curses_attr(theme.COLOR_INFORMATION_BAR))
        else:
            self.addstr(self.message,
                        to_curses_attr(theme.COLOR_INFORMATION_BAR))
        if window:
            self.print_scroll_position(window)
        self.finish_line(theme.COLOR_INFORMATION_BAR)
        self._refresh()


class ConversationInfoWin(InfoWin):
    """
    The line above the information window, displaying information
    about the user we are talking to
    """

    __slots__ = ()

    def __init__(self) -> None:
        InfoWin.__init__(self)

    def refresh(self, jid: JID | str, contact: Contact | None,
                window: 'TextWin', chatstate: str | None,
                information: Mapping[str, AdditionalCb]) -> None:
        # contact can be None, if we receive a message
        # from someone not in our roster. In this case, we display
        # only the maximum information from the message we can get.
        log.debug('Refresh: %s', self.__class__.__name__)
        try:
            jid = JID(jid)
        except InvalidJID:
            jid = JID('')
        if contact:
            if jid.resource:
                resource = contact[jid.full]
            else:
                resource = contact.get_highest_priority_resource()
        else:
            resource = None
        # if contact is None, then resource is None too:
        # user is not in the roster so we know almost nothing about it
        # If contact is a Contact, then
        # resource can now be a Resource: user is in the roster and online
        # or resource is None: user is in the roster but offline
        self._win.erase()
        if config.getbool('show_jid_in_conversations'):
            self.write_contact_jid(jid)
        self.write_contact_information(contact)
        self.write_resource_information(resource)
        self.print_scroll_position(window)
        self.write_chatstate(chatstate)
        self.write_additional_information(information, jid)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()

    def write_additional_information(self, information: Mapping[str, AdditionalCb], jid: JID) -> None:
        """
        Write all information added by plugins by getting the
        value returned by the callbacks.
        """
        color = to_curses_attr(get_theme().COLOR_INFORMATION_BAR)
        for plugin in information.values():
            self.addstr(plugin(str(jid)), color)

    def write_resource_information(self, resource: "Resource | None") -> None:
        """
        Write the information about the resource
        """
        if not resource:
            presence = "unavailable"
        else:
            presence = resource.presence
        theme = get_theme()
        color = theme.color_show(presence)
        if not presence:
            presence = theme.CHAR_STATUS
        self.addstr('[', to_curses_attr(theme.COLOR_INFORMATION_BAR))
        self.addstr(presence, to_curses_attr(color))
        if resource and resource.status:
            shortened = resource.status[:20] + (resource.status[:20] and '…')
            self.addstr(' %s' % shortened,
                        to_curses_attr(theme.COLOR_INFORMATION_BAR))
        self.addstr(']', to_curses_attr(theme.COLOR_INFORMATION_BAR))

    def write_contact_information(self, contact: Contact | None) -> None:
        """
        Write the information about the contact
        """
        theme = get_theme()
        color = to_curses_attr(theme.COLOR_INFORMATION_BAR)
        name_color: int | TextStyle
        if config.get('autocolor_tab_names') and contact is not None:
            name_color = (
                ccg_text_to_color(theme.ccg_palette, str(contact.bare_jid)),
                -1,
                theme.MODE_TAB_NAME,
            )
        else:
            name_color = color

        if not contact:
            self.addstr("(contact not in roster)", color)
            return
        display_name = contact.name
        if display_name:
            self.addstr('%s ' % (display_name), name_color)

    def write_contact_jid(self, jid: JID) -> None:
        """
        Just write the jid that we are talking to
        """
        theme = get_theme()
        color: int = to_curses_attr(theme.COLOR_INFORMATION_BAR)
        name_color: TextStyle
        if config.get('autocolor_tab_names'):
            name_color = (
                ccg_text_to_color(theme.ccg_palette, str(jid)),
                -1,
                theme.MODE_TAB_NAME,
            )
        else:
            name_color = theme.COLOR_CONVERSATION_NAME

        self.addstr('[', color)
        self.addstr(jid.full, to_curses_attr(name_color))
        self.addstr('] ', color)

    def write_chatstate(self, state: str | None) -> None:
        if state:
            self.addstr(
                ' %s' % (state, ),
                to_curses_attr(get_theme().COLOR_INFORMATION_BAR),
            )


class DynamicConversationInfoWin(ConversationInfoWin):
    __slots__ = ()

    def write_contact_jid(self, jid: JID) -> None:
        """
        Just displays the resource in an other color
        """
        log.debug("write_contact_jid DynamicConversationInfoWin, jid: %s",
                  jid.resource)
        theme = get_theme()
        color = to_curses_attr(theme.COLOR_INFORMATION_BAR)
        self.addstr('[', color)
        self.addstr(jid.bare,
                    to_curses_attr(theme.COLOR_CONVERSATION_NAME))
        if jid.resource:
            self.addstr(
                "/%s" % (jid.resource, ),
                to_curses_attr(theme.COLOR_CONVERSATION_RESOURCE))
        self.addstr('] ', color)


class MucInfoWin(InfoWin):
    """
    The line just above the information window, displaying information
    about the MUC we are viewing
    """

    __slots__ = ()

    def refresh(
        self,
        room: MucTab,
        window: Optional[TextWin] = None,
        user: Optional[User] = None,
        information: Optional[Dict[str, Any]] = None
    ) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        self.write_room_name(room)
        self.write_participants_number(room)
        self.write_own_nick(room)
        self.write_disconnected(room)
        self.write_role(user)
        if information:
            self.write_additional_information(information, room.name)
        if window:
            self.print_scroll_position(window)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()

    def write_additional_information(self, information: dict[str, AdditionalCb], jid: JID | str) -> None:
        """
        Write all information added by plugins by getting the
        value returned by the callbacks.
        """
        color = to_curses_attr(get_theme().COLOR_INFORMATION_BAR)
        for plugin in information.values():
            self.addstr(plugin(str(jid)), color)

    def write_room_name(self, room: 'MucTab') -> None:
        theme = get_theme()
        color = to_curses_attr(theme.COLOR_INFORMATION_BAR)
        label_color: TextStyle = theme.COLOR_GROUPCHAT_NAME

        if config.get('autocolor_tab_names'):
            label_color = ccg_text_to_color(
                theme.ccg_palette,
                room.jid.bare,
            ), -1, theme.MODE_TAB_NAME

        self.addstr('[', color)
        self.addstr(room.name,
                    to_curses_attr(label_color))
        self.addstr(']', color)

    def write_participants_number(self, room: 'MucTab') -> None:
        theme = get_theme()
        color = to_curses_attr(theme.COLOR_INFORMATION_BAR)
        self.addstr('{', color)
        self.addstr(
            str(len(room.users)),
            to_curses_attr(theme.COLOR_GROUPCHAT_NAME))
        self.addstr('} ', color)

    def write_disconnected(self, room: 'MucTab') -> None:
        """
        Shows a message if the room is not joined
        """
        if not room.joined:
            self.addstr(' -!- Not connected ',
                        to_curses_attr(get_theme().COLOR_INFORMATION_BAR))

    def write_own_nick(self, room: 'MucTab') -> None:
        """
        Write our own nick in the info bar
        """
        nick = room.own_nick
        if not nick:
            return
        self.addstr(
            truncate_nick(nick, 13),
            to_curses_attr(get_theme().COLOR_INFORMATION_BAR))

    def write_role(self, user: 'User' | None) -> None:
        """
        Write our own role and affiliation
        """
        if not user:
            return
        txt = ' ('
        if user.affiliation != 'none':
            txt += user.affiliation + ', '
        txt += user.role + ')'
        self.addstr(txt, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))


class ConversationStatusMessageWin(InfoWin):
    """
    The upper bar displaying the status message of the contact
    """

    __slots__ = ()

    def refresh(self, jid: JID | str, contact: Contact | None) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        try:
            jid = JID(jid)
        except InvalidJID:
            jid = JID('')
        if contact:
            if jid.resource:
                resource = contact[jid.full]
            else:
                resource = contact.get_highest_priority_resource()
        else:
            resource = None
        self._win.erase()
        if resource:
            self.write_status_message(resource)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()

    def write_status_message(self, resource: Resource) -> None:
        self.addstr(resource.status,
                    to_curses_attr(get_theme().COLOR_INFORMATION_BAR))


class BookmarksInfoWin(InfoWin):
    __slots__ = ()

    def refresh(self, preferred: str) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        self.write_remote_status(preferred)
        self.finish_line(get_theme().COLOR_INFORMATION_BAR)
        self._refresh()

    def write_remote_status(self, preferred: str) -> None:
        self.addstr('Remote storage: %s' % preferred,
                    to_curses_attr(get_theme().COLOR_INFORMATION_BAR))


class ConfirmStatusWin(Win):
    __slots__ = ('text', 'critical')

    def __init__(self, text: str, critical: bool = False) -> None:
        Win.__init__(self)
        self.text = text
        self.critical = critical

    def refresh(self) -> None:
        log.debug('Refresh: %s', self.__class__.__name__)
        self._win.erase()
        theme = get_theme()
        color: TextStyle
        if self.critical:
            color = theme.COLOR_WARNING_PROMPT
        else:
            color = theme.COLOR_INFORMATION_BAR
        c_color = to_curses_attr(color)
        self.addstr(self.text, c_color)
        self.finish_line(color)
        self._refresh()
