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

import sys
import os
import subprocess
from optparse import OptionParser
import ConfigParser
import logging
import multiprocessing as mp
import signal
import time
import urllib2
from httplib import BadStatusLine

import utils


LOG = logging.getLogger('suggest')


class Tailer(object):
    def __init__(self, log_queue, tailer_queue, filename, tmpdir):
        self.log_queue = log_queue
        self.tailer_queue = tailer_queue
        self.filename = filename
        self.tmpdir = tmpdir
        path = str(os.path.abspath(__file__))
        self.smtailer_path = os.path.join(os.path.dirname(path), 'suggestmtail.sh')

    def run(self):
        utils.setup_log_handler(self.log_queue, logging.DEBUG)
        prev_ent = ''
        while True:
            p = subprocess.Popen('%s %s 0 %s' % (self.smtailer_path, self.filename, self.tmpdir), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            try:
                res, error = p.communicate()
                if error:
                    LOG.error('Error in tailer: %s', error)
                    result = []
                else:
                    nbytes, res = res.split('\n', 1)
                    nbytes = int(nbytes)
                    res = res[:nbytes]
                    result = res.split('\n')
                    result[0] = prev_ent + result[0]
                    prev_ent = result[-1]
                    result = result[:-1]
                    result = [i for i in result if i]
            except:
                result = []

            try:
                os.kill(p.pid, signal.SIGKILL)
            except:
                pass

            LOG.debug('tailer result %s', len(result))
            if len(result) != 0:
                self.tailer_queue.put(result, block=True)
            else:
                time.sleep(5)


class Getter(object):
    def __init__(self, log_queue, tailer_queue, sender_queue):
        self.log_queue = log_queue
        self.tailer_queue = tailer_queue
        self.sender_queue = sender_queue

    def run(self):
        utils.setup_log_handler(self.log_queue, logging.DEBUG)
        while True:
            data = self.tailer_queue.get(block=True)

            to_send = 0
            for suggest_message in utils.parse_log(data):
                self.sender_queue.put(suggest_message)
                to_send += 1

            LOG.debug('count line to send %s', to_send)


def sender(args):
    try:
        sender_queue, base_url, service, corp_service, corp_bases, timeout = args

        while True:
            suggest_message = sender_queue.get(block=True)

            body, headers = suggest_message.create_http_request_body_headers()
            req_text = suggest_message.ltext
            if len(suggest_message.emails) > 0 and ','.join(suggest_message.emails) != suggest_message.ltext:
                req_text += ' emails: ' + ','.join(suggest_message.emails)

            if suggest_message.get_user_db() in corp_bases:
                indexer_url = base_url % corp_service
            else:
                indexer_url = base_url % service

            request = urllib2.Request(indexer_url, body, headers)

            send = True
            while send:
                try:
                    if int(suggest_message.keyprefix) > 0:
                        #LOG.debug('send message: body %s, headers %s'.replace('\n', ''), body, headers)

                        res = urllib2.urlopen(request, timeout=timeout)
                        LOG.info('%s %s, code = 200' % (req_text, suggest_message.keyprefix))
                    send = False

                except urllib2.HTTPError as e:
                    if e.code not in [500, 502, 503, 504]:
                        LOG.error('Document failed with error: code = %s, response = %s; '
                        'message: headers %s, body %s', e.code, e.read(), headers, body)
                        send = False
                    else:
                        LOG.warn('Document failed with error: code = %s, response = %s', e.code, e.read())
                        time.sleep(2)

                except urllib2.URLError as e:
                    LOG.warn('Document failed with error: %s, %s', e, e.reason)
                except BadStatusLine as e:
                    LOG.error('Bad status %s, message: headers %s, body %s', e, headers, body.replace('\n', '\\n'))
                except Exception as e:
                    LOG.error('Some strange error %s, message: headers %s, body %s', e, headers, body.replace('\n', '\\n'), exc_info=sys.exc_info())
                    send = False

    except KeyboardInterrupt:
        pass


def main(process, base_url, service, corp_service, corp_bases, timeout, filename, logname, tmpdir):
    manager = mp.Manager()
    ### log listener-writer configure
    log_queue = manager.Queue(10000)
    qlistener = utils.setup_log_listener_writer(log_queue, {'log': logname})
    qlistener.start()
    ###
    utils.setup_log_handler(log_queue, logging.DEBUG)

    tailer_queue = manager.Queue(10000)
    sender_queue = manager.Queue(10000)

    tailer = Tailer(log_queue, tailer_queue, filename, tmpdir)
    tailer_proc = mp.Process(target=tailer.run)
    tailer_proc.start()

    getter = Getter(log_queue, tailer_queue, sender_queue)
    getter_proc = mp.Process(target=getter.run)
    getter_proc.start()

    sender_pool = mp.Pool(processes=process)
    res = [sender_pool.map(sender, [(sender_queue, base_url, service, corp_service, corp_bases, timeout) for i in range(process)])]

    sender_pool.close()
    sender_pool.join()

    # forward termination to childs
    childs = [tailer_proc, getter_proc]

    def hook(signum, frame):
        LOG.info('recieved signal %s, terminating childs', signum)
        [proc.terminate() for proc in childs]

    signal.signal(signal.SIGINT, hook)
    signal.signal(signal.SIGTERM, hook)
    signal.signal(signal.SIGQUIT, hook)

    # wait for childs to stop execution
    [proc.join() for proc in childs]

    LOG.debug('done index')

    qlistener.stop()


if __name__ == "__main__":
    parser = OptionParser()

    parser.add_option("-c", "--config", dest="config",
        help="config filename")
    parser.add_option("-d", "--tmpdir", dest="tmpdir",
        help="temp directory")
    parser.add_option("-f", "--filename", dest="filename",
        help="log file name to process")
    parser.add_option("-l", "--logname", dest="logname",
        help="output log file name")

    options, args = parser.parse_args()

    if not options.config:
        parser.error("option -c required")

    if not options.tmpdir:
        parser.error("option -d required")
    tmpdir = options.tmpdir

    defaults = {
        "process": 3,
        "timeout": 20,
    }
    config = ConfigParser.RawConfigParser(defaults)
    config.read(options.config)

    filename = options.filename
    if not filename:
        if not config.has_option("realtime_suggest", "filename"):
            raise RuntimeError('Add the filename via -f option or to config.')
        else:
            filename = config.get("realtime_suggest", "filename")

    logname = options.logname
    if not logname:
        if not config.has_option("realtime_suggest", "logname"):
            raise RuntimeError('Add the oputup log path via -l option or to config.')
        else:
            logname = config.get("realtime_suggest", "logname")

    process = config.getint("realtime_suggest", "process")

    if not config.has_option("realtime_suggest", "host"):
        raise RuntimeError('Add the host name for sending documents to index in the configuration.')
    host = config.get("realtime_suggest", "host")

    if not config.has_option("realtime_suggest", "port"):
        raise RuntimeError('Add the port for sending documents to index in the configuration.')
    port = config.getint("realtime_suggest", "port")

    timeout = config.getint("realtime_suggest", "timeout")

    if not config.has_option("realtime_suggest", "corp_bases"):
        raise RuntimeError('Add the names of corporate databases in the configuration, like corp_bases = mdb100.')
    corp_bases = config.get("realtime_suggest", "corp_bases").split()

    if not config.has_option("realtime_suggest", "service"):
        raise RuntimeError('Add the hash of service name in the configuration.')

    service = config.get("realtime_suggest", "service")

    if not config.has_option("realtime_suggest", "corp_service"):
        raise RuntimeError('Add the hash of corporate service name in the configuration.')

    corp_service = config.get("realtime_suggest", "corp_service")

    base_url = 'http://%s:%s/service/%%s' % (host, port)

    main(
        process,
        base_url,
        service,
        corp_service,
        corp_bases,
        timeout,
        filename,
        logname,
        tmpdir,
    )
