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

from __future__ import absolute_import

from io import BytesIO
from json import dumps, loads

from core import output
from core.config import CONFIG
from core.tools import decode, geolocate, to_bytes

from geoip2.database import Reader

from twisted.internet import reactor
from twisted.internet.ssl import ClientContextFactory
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.host = CONFIG.get('output_nlcvapi', 'host', fallback='https://api.nlcv.bas.bg/v1.0/honeypot')
        self.geoip = CONFIG.getboolean('output_nlcvapi', 'geoip', fallback=True)
        contextFactory = WebClientContextFactory()
        pool = HTTPConnectionPool(reactor)
        pool._factory = QuietHTTP11ClientFactory
        self.agent = Agent(reactor, contextFactory=contextFactory, pool=pool)

        if self.geoip:
            geoipdb_city_path = CONFIG.get('output_nlcvapi', 'geoip_citydb', fallback='data/GeoLite2-City.mmdb')
            geoipdb_asn_path = CONFIG.get('output_nlcvapi', 'geoip_asndb', fallback='data/GeoLite2-ASN.mmdb')
            try:
                self.reader_city = Reader(geoipdb_city_path)
            except Exception:
                self.reader_city = None
                msg('Failed to open City GeoIP database {}'.format(geoipdb_city_path))
            try:
                self.reader_asn = Reader(geoipdb_asn_path)
            except Exception:
                self.reader_asn = None
                msg('Failed to open ASN GeoIP database {}'.format(geoipdb_asn_path))

    def stop(self):
        if self.geoip:
            if getattr(self, 'reader_city', None) is not None:
                self.reader_city.close()
            if getattr(self, 'reader_asn', None) is not None:
                self.reader_asn.close()

    def write(self, event):
        event['honeypot'] = 'siphoney'
        if self.geoip:
            country, country_code, city, org, asn_num = geolocate(event['src_ip'], self.reader_city, self.reader_asn)
            event['country'] = country
            event['country_code'] = country_code
            event['city'] = city
            event['org'] = org
            event['asn'] = asn_num
        self._postentry(event)

    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.response)

        def cbResponse(response):
            if response.code in (200, 201):
                return
            msg('NLCVAPI response: {}: "{}"'.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):
            try:
                j = loads(result)
                msg('NLCVAPI response: {}'.format(j.get('message', '')))
            except Exception:
                pass

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