"""IRC-related functions and classes to be imported by irc.py"""
import logging
import base64

PchumLog = logging.getLogger("pchumLogger")


class SendIRC:
    """Provides functions for outgoing IRC commands.

    Functions are protocol compliant but don't implement all valid uses of certain commands.
    """

    def __init__(self):
        self.socket = None  # INET socket connected with server.

    def _send(self, *args: str, text=None):
        """Send a command to the IRC server.

        Takes either a string or a list of strings.
        The 'text' argument is for the final parameter, which can have spaces.

        Since this checks if the socket is alive, it's best to send via this method."""
        # Return if disconnected
        if not self.socket or self.socket.fileno() == -1:
            PchumLog.error(
                "Send attempted while disconnected, args: %s, text: %s.", args, text
            )
            return

        command = ""
        # Convert command arguments to a single string if passed.
        if args:
            command += " ".join(args)
        # If text is passed, add ':' to imply everything after it is one parameter.
        if text:
            command += f" :{text}"
        # Add characters for end of line in IRC.
        command += "\r\n"
        # UTF-8 is the prefered encoding in 2023.
        outgoing_bytes = command.encode(encoding="utf-8", errors="replace")

        try:
            PchumLog.debug("Sending: %s", command)
            self.socket.send(outgoing_bytes)
        except OSError:
            PchumLog.exception("Error while sending: '%s'", command.strip())
            self.socket.close()

    def ping(self, token):
        """Send PING command to server to check for connectivity."""
        self._send("PING", text=token)

    def pong(self, token):
        """Send PONG command to reply to server PING."""
        self._send("PONG", token)

    def pass_(self, password):
        """Send a 'connection password' to the server.

        Function is 'pass_' because 'pass' is reserved."""
        self._send("PASS", text=password)

    def nick(self, nick):
        """Send USER command to communicate nick to server."""
        self._send("NICK", nick)

    def user(self, username, realname):
        """Send USER command to communicate username and realname to server."""
        self._send("USER", username, "0", "*", text=realname)

    def privmsg(self, target, text):
        """Send PRIVMSG command to send a message."""
        for line in text.split("\n"):
            self._send("PRIVMSG", target, text=line)

    def names(self, channel):
        """Send NAMES command to view channel members."""
        self._send("NAMES", channel)

    def kick(self, channel, user, reason=""):
        """Send KICK command to force user from channel."""
        if reason:
            self._send(f"KICK {channel} {user}", text=reason)
        else:
            self._send(f"KICK {channel} {user}")

    def mode(self, target, modestring="", mode_arguments=""):
        """Set or remove modes from target."""
        outgoing_mode = " ".join([target, modestring, mode_arguments]).strip()
        self._send("MODE", outgoing_mode)

    def ctcp(self, target, command, msg=""):
        """Send Client-to-Client Protocol message."""
        outgoing_ctcp = " ".join(
            [command, msg]
        ).strip()  # Extra spaces break protocol, so strip.
        self.privmsg(target, f"\x01{outgoing_ctcp}\x01")

    def ctcp_reply(self, target, command, msg=""):
        """Send Client-to-Client Protocol reply message, responding to a CTCP message."""
        outgoing_ctcp = " ".join(
            [command, msg]
        ).strip()  # Extra spaces break protocol, so strip.
        self.notice(target, f"\x01{outgoing_ctcp}\x01")

    def metadata(self, target, subcommand, *params):
        """Send Metadata command to get or set metadata.

        See IRC metadata draft specification:
        https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237
        """
        self._send("METADATA", target, subcommand, *params)

    def cap(self, subcommand, *params):
        """Send IRCv3 CAP command for capability negotiation.

        See: https://ircv3.net/specs/extensions/capability-negotiation.html"""
        self._send("CAP", subcommand, *params)

    def join(self, channel, key=""):
        """Send JOIN command to join a channel/memo.

        Keys or joining multiple channels is possible in the specification, but unused.
        """
        channel_and_key = " ".join([channel, key]).strip()
        self._send("JOIN", channel_and_key)

    def part(self, channel):
        """Send PART command to leave a channel/memo.

        Providing a reason or leaving multiple channels is possible in the specification.
        """
        self._send("PART", channel)

    def notice(self, target, text):
        """Send a NOTICE to a user or channel."""
        self._send("NOTICE", target, text=text)

    def invite(self, nick, channel):
        """Send INVITE command to invite a user to a channel."""
        self._send("INVITE", nick, channel)

    def away(self, text=None):
        """AWAY command to mark client as away or no longer away.

        No 'text' parameter means the client is no longer away."""
        if text:
            self._send("AWAY", text=text)
        else:
            self._send("AWAY")

    def list(self):
        """Send LIST command to get list of channels."""
        self._send("LIST")

    def quit(self, reason=""):
        """Send QUIT to terminate connection."""
        self._send("QUIT", text=reason)

    def authenticate(self, token=None, nick=None, password=None):
        """Authenticate command for SASL.

        Send either a token like 'PLAIN' or authenticate with nick and password.

        Reference: https://ircv3.net/irc/#account-authentication-and-registration
        """
        if token:
            self._send("AUTHENTICATE", text=token)
            return
        if nick and password:
            # Authentication identity 'nick', authorization identity 'nick' and password 'password'.
            sasl_string = f"{nick}\x00{nick}\x00{password}"
            # Encode to use base64, then decode since 'text' only takes str.
            sasl_string_bytes = sasl_string.encode(encoding="utf-8", errors="replace")
            sasl_string_base64 = base64.b64encode(sasl_string_bytes).decode(
                encoding="utf-8"
            )
            # SASL string needs to be under 400 bytes,
            # splitting over multiple messages is in the protocol but not implemented here.
            self._send("AUTHENTICATE", text=sasl_string_base64)


def parse_irc_line(line: str):
    """Retrieves tags, prefix, command, and arguments from an unparsed IRC line."""
    parts = line.split(" ")
    tags = None
    prefix = None
    if parts[0].startswith(":"):
        prefix = parts[0][1:]
        command = parts[1]
        args = parts[2:]
    elif parts[0].startswith("@"):
        tags = parts[0]  # IRCv3 message tag
        prefix = parts[1][1:]
        command = parts[2]
        args = parts[3:]
    else:
        command = parts[0]
        args = parts[1:]
    command = command.casefold()

    # If ':' is present the subsequent args are one parameter.
    fused_args = []
    for idx, arg in enumerate(args):
        if arg.startswith(":"):
            final_param = " ".join(args[idx:])
            fused_args.append(final_param[1:])
            break
        fused_args.append(arg)

    return (tags, prefix, command, fused_args)