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

"""
:mod:`subscriber` -- Subscribe to a redis channel and gather logging messages.

To use this module, you have to define at least a channel name.
"""


import redis
from logbook import Logger
import ConfigParser
from logbook import NestedSetup
from logbook import NullHandler
from logbook import TimedRotatingFileHandler
from logbook import MailHandler
import os

# use a TCP Socket by default
use_tcp_socket = True

# default config for a UNIX socket
unix_socket = '/tmp/redis.sock'
# default config for a TCP socket
hostname = 'localhost'
port = 6379

pubsub = None
channel = None

# Required only if you want to send emails
dest_mails = []
smtp_server = None
smtp_port = 0
src_server = None


def setup(name, path='log', enable_debug=False):
    """
    Prepare a NestedSetup.

    :param name: the channel name
    :param path: the path where the logs will be written
    :param enable_debug: do we want to save the message at the DEBUG level

    :return a nested Setup
    """
    path_tmpl = os.path.join(path, '{name}_{level}.log')
    info = path_tmpl.format(name=name, level='info')
    warn = path_tmpl.format(name=name, level='warn')
    err = path_tmpl.format(name=name, level='err')
    crit = path_tmpl.format(name=name, level='crit')
    # a nested handler setup can be used to configure more complex setups
    setup = [
        # make sure we never bubble up to the stderr handler
        # if we run out of setup handling
        NullHandler(),
        # then write messages that are at least info to to a logfile
        TimedRotatingFileHandler(info, level='INFO', encoding='utf-8',
                                 date_format='%Y-%m-%d'),
        # then write messages that are at least warnings to to a logfile
        TimedRotatingFileHandler(warn, level='WARNING', encoding='utf-8',
                                 date_format='%Y-%m-%d'),
        # then write messages that are at least errors to to a logfile
        TimedRotatingFileHandler(err, level='ERROR', encoding='utf-8',
                                 date_format='%Y-%m-%d'),
        # then write messages that are at least critical errors to to a logfile
        TimedRotatingFileHandler(crit, level='CRITICAL', encoding='utf-8',
                                 date_format='%Y-%m-%d'),
    ]
    if enable_debug:
        debug = path_tmpl.format(name=name, level='debug')
        setup.insert(1, TimedRotatingFileHandler(debug, level='DEBUG',
                     encoding='utf-8', date_format='%Y-%m-%d'))
    if src_server is not None and smtp_server is not None \
            and smtp_port != 0 and len(dest_mails) != 0:
        mail_tmpl = '{name}_error@{src}'
        from_mail = mail_tmpl.format(name=name, src=src_server)
        subject = 'Error in {}'.format(name)
        # errors should then be delivered by mail and also be kept
        # in the application log, so we let them bubble up.
        setup.append(MailHandler(from_mail, dest_mails, subject,
                                 level='ERROR', bubble=True,
                                 server_addr=(smtp_server, smtp_port)))

    return NestedSetup(setup)


def mail_setup(path):
    """
    Set the variables to be able to send emails.

    :param path: path to the config file
    """
    global dest_mails
    global smtp_server
    global smtp_port
    global src_server
    config = ConfigParser.RawConfigParser()
    config.readfp(path)
    dest_mails = config.get('mail', 'dest_mail').split(',')
    smtp_server = config.get('mail', 'smtp_server')
    smtp_port = config.get('mail', 'smtp_port')
    src_server = config.get('mail', 'src_server')


def run(log_name, path, debug=False, mail=None):
    """
    Run a subscriber and pass the messages to the logbook setup.
    Stays alive as long as the pubsub instance listen to something.

    :param log_name: the channel to listen to
    :param path: the path where the log files will be written
    :param debug: True if you want to save the debug messages too
    :param mail: Path to the config file for the mails

    """
    global pubsub
    global channel
    channel = log_name
    if use_tcp_socket:
        r = redis.StrictRedis(host=hostname, port=port)
    else:
        r = redis.StrictRedis(unix_socket_path=unix_socket)
    pubsub = r.pubsub()
    pubsub.psubscribe(channel + '.*')

    logger = Logger(channel)
    if mail is not None:
        mail_setup(mail)
    if os.path.exists(path) and not os.path.isdir(path):
        raise Exception("The path you want to use to save the file is invalid (not a directory).")
    if not os.path.exists(path):
        os.mkdir(path)
    with setup(channel, path, debug):
        for msg in pubsub.listen():
            if msg['type'] == 'pmessage':
                level = msg['channel'].split('.')[1]
                message = msg['data']
                try:
                    message = message.decode('utf-8')
                except:
                    pass
                logger.log(level, message)


def stop():
    """
    Unsubscribe to the channel, stop the script.
    """
    pubsub.punsubscribe(channel + '.*')