from datetime import datetime

import main
from core.relay import sendRelayNotification
from modules import userinfo
from twisted.internet import reactor
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.internet.ssl import DefaultOpenSSLContextFactory
from twisted.words.protocols.irc import IRCClient
from utils.get import getRelay
from utils.logging.log import error, log
from utils.logging.send import sendAll
from utils.parsing import parsen


# TODO: strip out non-relay functionality
class IRCRelay(IRCClient):
    def __init__(self, num, relayCommands, user, stage2):
        self.isconnected = False
        self.buffer = ""
        if user is None:
            self.user = main.config["Relay"]["User"]
        else:
            self.user = user.lower()
        password = main.config["Relay"]["Password"]
        self.nickname = "relay"
        self.realname = "relay"
        self.username = self.user
        self.password = self.user + ":" + password

        self.relayCommands = relayCommands
        self.num = num
        self.stage2 = stage2
        self.loop = None

    def privmsg(self, user, channel, msg):
        nick, ident, host = parsen(user)
        for i in main.ZNCErrors:
            if i in msg:
                error("ZNC issue:", msg)
        if nick[0] == main.config["Tweaks"]["ZNC"]["Prefix"]:
            nick = nick[1:]
            if nick in self.relayCommands.keys():
                sendAll("[%s] %s -> %s" % (self.num, nick, msg))

    def irc_ERR_PASSWDMISMATCH(self, prefix, params):
        log("%s: relay password mismatch" % self.num)
        sendAll("%s: relay password mismatch" % self.num)

    def sendStage2(self):
        # [["user", {"sasl": ["message1", "message2"]}], []]
        if not len(self.stage2) == 0:
            user = self.stage2[0].pop(0)
            commands = self.stage2[0].pop(0)
            del self.stage2[0]
            deliverRelayCommands(self.num, commands, user, self.stage2)

    def signedOn(self):
        if not self.isconnected:
            self.isconnected = True
            # log("signed on as a relay: %s" % self.num)
            sleeptime = 0
            increment = 0.8
            for i in self.relayCommands.keys():
                for x in self.relayCommands[i]:
                    reactor.callLater(
                        sleeptime,
                        self.msg,
                        main.config["Tweaks"]["ZNC"]["Prefix"] + i,
                        x,
                    )
                sleeptime += increment
                increment += 0.8
            if self.stage2 is not None:
                reactor.callLater(sleeptime, self.sendStage2)
            reactor.callLater(sleeptime + 5, self.transport.loseConnection)
            return


class IRCRelayFactory(ReconnectingClientFactory):
    def __init__(self, net, num=None, relayCommands=None, user=None, stage2=None):
        if net is None:
            self.num = num
            self.net = None
            self.name = "relay - %i" % num
            self.relay = True
        else:
            self.name = net + str(num)
            self.num = num
            self.net = net
            self.relay = False
        self.client = None
        self.maxDelay = main.config["Tweaks"]["Delays"]["MaxDelay"]
        self.initialDelay = main.config["Tweaks"]["Delays"]["InitialDelay"]
        self.factor = main.config["Tweaks"]["Delays"]["Factor"]
        self.jitter = main.config["Tweaks"]["Delays"]["Jitter"]

        self.relayCommands, self.user, self.stage2 = relayCommands, user, stage2

    def buildProtocol(self, addr):

        entry = IRCRelay(self.num, self.relayCommands, self.user, self.stage2)

        self.client = entry
        return entry

    def clientConnectionLost(self, connector, reason):
        if not self.relay:
            userinfo.delChannels(self.net, self.client.channels)
        if self.client is not None:
            self.client.isconnected = False
            self.client.authenticated = False
            self.client.channels = []
        error = reason.getErrorMessage()
        if not self.relay:
            log("%s - %i: connection lost: %s" % (self.net, self.num, error))
            sendAll("%s - %i: connection lost: %s" % (self.net, self.num, error))
            ctime = str(datetime.now().isoformat())
            sendRelayNotification(
                {
                    "type": "conn",
                    "net": self.net,
                    "num": self.num,
                    "status": "lost",
                    "message": error,
                    "ts": ctime,
                }
            )
            self.retry(connector)
        # ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        if self.client is not None:
            self.client.isconnected = False
            self.client.authenticated = False
            self.client.channels = []
        error = reason.getErrorMessage()
        log("%s - %i: connection failed: %s" % (self.net, self.num, error))
        if not self.relay:
            sendAll("%s - %s: connection failed: %s" % (self.net, self.num, error))
            ctime = str(datetime.now().isoformat())
            sendRelayNotification(
                {
                    "type": "conn",
                    "net": self.net,
                    "num": self.num,
                    "status": "failed",
                    "message": error,
                    "ts": ctime,
                }
            )
            self.retry(connector)
        # ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)


def deliverRelayCommands(num, relayCommands, user=None, stage2=None):
    keyFN = main.certPath + main.config["Key"]
    certFN = main.certPath + main.config["Certificate"]
    contextFactory = DefaultOpenSSLContextFactory(
        keyFN.encode("utf-8", "replace"), certFN.encode("utf-8", "replace")
    )
    bot = IRCRelayFactory(
        net=None, num=num, relayCommands=relayCommands, user=user, stage2=stage2
    )
    host, port = getRelay(num)
    reactor.connectSSL(host, port, bot, contextFactory)
