import configparser
import os
import sys
import argparse
import time
import logging
import re
import socket

from influxdb import InfluxDBClient
from influxdb.exceptions import InfluxDBClientError, InfluxDBServerError


# TODO Move urlopen login in each method call to one central method
# TODO Validate that we get a valid URL from config
# TODO Deal with multiple trackers per torrent

__author__ = 'barry'
class configManager():

    #TODO Validate given client url

    def __init__(self, silent, config):

        self.valid_torrent_clients = ['deluge', 'utorrent', 'rtorrent']
        self.valid_log_levels = {
            'DEBUG': 0,
            'INFO': 1,
            'WARNING': 2,
            'ERROR': 3,
            'CRITICAL': 4
        }
        self.silent = silent

        if not self.silent:
            print('Loading Configuration File {}'.format(config))

        config_file = os.path.join(os.getcwd(), config)
        if os.path.isfile(config_file):
            self.config = configparser.ConfigParser()
            self.config.read(config_file)
        else:
            print('ERROR: Unable To Load Config File: {}'.format(config_file))
            sys.exit(1)

        self._load_config_values()
        self._validate_logging_level()
        self._validate_torrent_client()
        if not self.silent:
            print('Configuration Successfully Loaded')

    def _load_config_values(self):

        # General
        self.delay = self.config['GENERAL'].getint('Delay', fallback=2)
        self.output = self.config['GENERAL'].getboolean('Output', fallback=True)
        self.hostname = self.config['GENERAL'].get('Hostname')
        if not self.hostname:
            self.hostname = socket.gethostname()


        # InfluxDB
        self.influx_address = self.config['INFLUXDB']['Address']
        self.influx_port = self.config['INFLUXDB'].getint('Port', fallback=8086)
        self.influx_database = self.config['INFLUXDB'].get('Database', fallback='speedtests')
        self.influx_user = self.config['INFLUXDB'].get('Username', fallback='')
        self.influx_password = self.config['INFLUXDB'].get('Password', fallback='')
        self.influx_ssl = self.config['INFLUXDB'].getboolean('SSL', fallback=False)
        self.influx_verify_ssl = self.config['INFLUXDB'].getboolean('Verify_SSL', fallback=True)

        #Logging
        self.logging = self.config['LOGGING'].getboolean('Enable', fallback=False)
        self.logging_level = self.config['LOGGING']['Level'].upper()
        self.logging_file = self.config['LOGGING']['LogFile']
        self.logging_censor = self.config['LOGGING'].getboolean('CensorLogs', fallback=True)
        self.logging_print_threshold = self.config['LOGGING'].getint('PrintThreshold', fallback=2)

        # TorrentClient
        self.tor_client = self.config['TORRENTCLIENT'].get('Client', fallback=None).lower()
        self.tor_client_user = self.config['TORRENTCLIENT'].get('Username', fallback=None)
        self.tor_client_password = self.config['TORRENTCLIENT'].get('Password', fallback=None)
        self.tor_client_url = self.config['TORRENTCLIENT'].get('Url', fallback=None)

    def _validate_torrent_client(self):

        if self.tor_client not in self.valid_torrent_clients:
            print('ERROR: {} Is Not a Valid or Support Torrent Client.  Aborting'.format(self.tor_client))
            sys.exit(1)

    def _validate_logging_level(self):
        """
        Make sure we get a valid logging level
        :return:
        """


        if self.logging_level in self.valid_log_levels:
            self.logging_level = self.logging_level.upper()
            return
        else:
            if not self.silent:
                print('Invalid logging level provided. {}'.format(self.logging_level))
                print('Logging will be disabled')
            self.logging = None


class influxdbSeedbox():

    def __init__(self, config=None, silent=None):

        self.config = configManager(silent, config=config)

        self.output = self.config.output
        self.logger = None
        self.delay = self.config.delay

        self.influx_client = InfluxDBClient(
            self.config.influx_address,
            self.config.influx_port,
            database=self.config.influx_database,
            ssl=self.config.influx_ssl,
            verify_ssl=self.config.influx_verify_ssl
        )
        self._set_logging()

        if self.config.tor_client == 'deluge':
            from clients.deluge import DelugeClient
            if self.output:
                print('Generating Deluge Client')
            self.tor_client = DelugeClient(self.send_log,
                                           username=self.config.tor_client_user,
                                           password=self.config.tor_client_password,
                                           url=self.config.tor_client_url,
                                           hostname=self.config.hostname)

        elif self.config.tor_client == 'utorrent':
            from clients.utorrent import UTorrentClient
            if self.output:
                print('Generating uTorrent Client')
            self.tor_client = UTorrentClient(self.send_log,
                                             username=self.config.tor_client_user,
                                             password=self.config.tor_client_password,
                                             url=self.config.tor_client_url,
                                             hostname=self.config.hostname)

        elif self.config.tor_client == 'rtorrent':
            from clients.rtorrent import rTorrentClient
            if self.output:
                print('Generating rTorrent Client')
            self.tor_client = rTorrentClient(self.send_log,
                                             username=None,
                                             password=None,
                                             url=self.config.tor_client_url,
                                             hostname=self.config.hostname)

    def _set_logging(self):
        """
        Create the logger object if enabled in the config
        :return: None
        """

        if self.config.logging:
            if self.output:
                print('Logging is enabled.  Log output will be sent to {}'.format(self.config.logging_file))
            self.logger = logging.getLogger(__name__)
            self.logger.setLevel(self.config.logging_level)
            formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
            fhandle = logging.FileHandler(self.config.logging_file)
            fhandle.setFormatter(formatter)
            self.logger.addHandler(fhandle)

    def send_log(self, msg, level):
        """
        Used as a shim to write log messages.  Allows us to sanitize input before logging
        :param msg: Message to log
        :param level: Level to log message at
        :return: None
        """

        if not self.logger:
            return

        if self.output and self.config.valid_log_levels[level.upper()] >= self.config.logging_print_threshold:
            print(msg)

        # Make sure a good level was given
        if not hasattr(self.logger, level):
            self.logger.error('Invalid log level provided to send_log')
            return

        output = self._sanitize_log_message(msg)

        log_method = getattr(self.logger, level)
        log_method(output)

    def _sanitize_log_message(self, msg):
        """
        Take the incoming log message and clean and sensitive data out
        :param msg: incoming message string
        :return: cleaned message string
        """

        if not self.config.logging_censor:
            return msg

        # Remove server addresses
        msg = msg.replace(self.config.tor_client_url, 'http://*******:8112/json')

        # Remove IP addresses
        for match in re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", msg):
            msg = msg.replace(match, '***.***.***.***')

        return msg

    def write_influx_data(self, json_data):
        """
        Writes the provided JSON to the database
        :param json_data:
        :return:
        """
        self.send_log(json_data, 'info')

        # TODO This bit of fuckery may turn out to not be a good idea.
        """
        The idea is we only write 1 series at a time but we can get passed a bunch of series.  If the incoming list
        has more than 1 thing, we know it's a bunch of points to write.  We loop through them and recursively call this
        method which each series.  The recursive call will only have 1 series so it passes on to be written to Influx.

        I know, brilliant right?  Probably not
        """
        if len(json_data) > 1:
            for series in json_data:
                self.write_influx_data(series)
            return

        try:
            self.influx_client.write_points(json_data)
        except (InfluxDBClientError, ConnectionError, InfluxDBServerError) as e:
            if hasattr(e, 'code') and e.code == 404:

                msg = 'Database {} Does Not Exist.  Attempting To Create'.format(self.config.influx_database)
                self.send_log(msg, 'error')

                # TODO Grab exception here
                self.influx_client.create_database(self.config.influx_database)
                self.influx_client.write_points(json_data)

                return

            self.send_log('Failed to write data to InfluxDB', 'error')

            print('ERROR: Failed To Write To InfluxDB')
            print(e)

        self.send_log('Written To Influx: {}'.format(json_data), 'debug')



    def run(self):
        while True:
            self.tor_client.get_all_torrents()
            torrent_json = self.tor_client.process_torrents()
            if torrent_json:
                self.write_influx_data(torrent_json)
            #self.tor_client.get_active_plugins()
            tracker_json = self.tor_client.process_tracker_list()
            if tracker_json:
                self.write_influx_data(tracker_json)
            time.sleep(self.delay)




def main():

    parser = argparse.ArgumentParser(description="A tool to send Torrent Client statistics to InfluxDB")
    parser.add_argument('--config', default='config.ini', dest='config', help='Specify a custom location for the config file')
    # Silent flag allows output prior to the config being loaded to also be suppressed
    parser.add_argument('--silent', action='store_true', help='Surpress All Output, regardless of config settings')
    args = parser.parse_args()
    monitor = influxdbSeedbox(silent=args.silent, config=args.config)
    monitor.run()


if __name__ == '__main__':
    main()