#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Simple Discord webhook logger
"""

from __future__ import absolute_import

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, to_bytes

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 = to_bytes(CONFIG.get('output_discord', 'url'))
        self.delay = CONFIG.getfloat('output_discord', 'delay', fallback=2.0)
        contextFactory = WebClientContextFactory()
        pool = HTTPConnectionPool(reactor)
        pool._factory = QuietHTTP11ClientFactory
        self.agent = Agent(reactor, contextFactory=contextFactory, pool=pool)
        self.last_sent = 0
        self.requests_list = []

    def stop(self):
        pass

    def write(self, event):
        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.requests_list.append({'content': message})
        self._drain()

    def _drain(self):
        """
        Send the next queued entry if the rate-limit window has elapsed.
        If entries remain, schedule another drain after self.delay seconds.
        """
        if not self.requests_list:
            return
        now = time()
        elapsed = now - self.last_sent
        if elapsed < self.delay:
            deferLater(reactor, self.delay - elapsed, self._drain)
            return
        self.last_sent = now
        entry = self.requests_list.pop(0)
        d = self._post(entry)
        if self.requests_list:
            deferLater(reactor, self.delay, self._drain)
        return d

    def _post(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 or response.code in [200, 201, 204]:
                return
            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:
                try:
                    j = loads(result)
                    msg('Discord response: {}'.format(j.get('message', '')))
                except Exception:
                    pass

        headers = Headers({b'Content-Type': [b'application/json']})
        body = FileBodyProducer(BytesIO(to_bytes(dumps(entry, sort_keys=True))))
        d = self.agent.request(b'POST', self.url, headers, body)
        d.addCallback(cbResponse)
        d.addErrback(cbError)
        return d
