"""
Simple Discord webhook logger
"""

from io import BytesIO
from json import dumps, loads
from time import gmtime, strftime, time

from core import output
from core.config import CONFIG
from core.tools import decode, encode

from twisted.internet import reactor
from twisted.internet.ssl import ClientContextFactory
from twisted.internet.task import deferLater
from twisted.python.log import msg
from twisted.web.client import Agent, FileBodyProducer, HTTPConnectionPool, _HTTP11ClientFactory, readBody
from twisted.web.http_headers import Headers


class WebClientContextFactory(ClientContextFactory):
    def getContext(self, hostname, port):
        return ClientContextFactory.getContext(self)


class QuietHTTP11ClientFactory(_HTTP11ClientFactory):
    noisy = False


class Output(output.Output):
    def start(self):
        self.url = encode(CONFIG.get('output_discord', 'url'))
        self.delay = CONFIG.getfloat('output_discord', 'delay', fallback=2.0)
        contextFactory = WebClientContextFactory()
        myQuietPool = HTTPConnectionPool(reactor)
        myQuietPool._factory = QuietHTTP11ClientFactory
        self.agent = Agent(reactor, contextFactory=contextFactory, pool=myQuietPool)
        self.last_sent = 0
        self.requests_list = []

    def stop(self):
        pass

    def write(self, event):
        webhook_message = '__New event__\n[{} UTC] [SIPHoney on {}]: {} from {}:{}.\n'.format(
            strftime('%Y-%m-%d %H:%M:%S', gmtime(event['unixtime'])),
            event['sensor'], event['message'], event['src_ip'], event['dst_port']
        )

        self.postentry({'content': webhook_message})

    def postentry(self, entry):
        def cbBody(body):
            return processResult(body)

        def cbPartial(failure):
            """
            Google HTTP Server does not set Content-Length. Twisted marks it as partial
            """
            failure.printTraceback()
            return processResult(failure.value)

        def cbResponse(response):
            if response is None:
                return
            if response.code in [200, 201, 204]:
                return
            else:
                msg('Discord error: {} {}'.format(response.code, decode(response.phrase)))
                d = readBody(response)
                d.addCallback(cbBody)
                d.addErrback(cbPartial)
                return d

        def cbError(failure):
            failure.printTraceback()

        def processResult(result):
            if result:
                j = loads(result)
                msg('Discord response: {}'.format(j['message']))

        now = time()
        self.requests_list.append(entry)
        if now - self.last_sent < self.delay:
            deferLater(reactor, self.delay, self.postentry, entry)
            return
        self.last_sent = now
        entry = self.requests_list.pop(0)
        headers = Headers(
            {
                b'Content-Type': [b'application/json'],
            }
        )
        body = FileBodyProducer(BytesIO(encode(dumps(entry))))
        d = self.agent.request(b'POST', self.url, headers, body)
        d.addCallback(cbResponse)
        d.addErrback(cbError)
        return d
