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


import re
import json
import socket
import sys
import hashlib
import logging
import pprint
from datetime import datetime

import flask

import indexer_proxy_lib.utils as utils
import indexer_proxy_lib.errors as errors
import indexer_proxy_lib.logs as logs


ERROR_TEMPLATE = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN
<title>{{ status_message }}</title>
<h1>{{ status_message }}</h1>
<p>{{ message }}</p>\n
"""
OK_TEMPLATE = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN
<title>200 OK</title>
<h1>200 OK</h1>
<p>OK</p>\n
"""
CONTENT_TYPE_RE = re.compile("^.*multipart/form-data.*$")


class ProxyApp(flask.Flask):
    def __init__(self, *args, **kwargs):
        self.sock = None
        self.services = {}
        self.services_status = None
        self.reload_time = 60
        super(ProxyApp, self).__init__(*args, **kwargs)

    def get_services(self, services_filename):
        def _get_services():
            try:
                _services_name = utils.parse_searchmap(open(services_filename, 'r'))
                # раскомментировать для рагрузочного тестирования
                #~ _services_name += ['fake1', 'fake2']
                self.services_status = datetime.now()
                self.services = dict([(hashlib.md5('%s %s' % (service, app.config.get('SERVICES_SECRET_KEY'))).hexdigest(), service)
                    for service in _services_name])

            except IOError, e:
                self.logger.warning('file not found: %s', e)
                self.services_status = datetime.now()
                self.services = {}

        if self.services_status:
            real_reload_time = (datetime.now() - self.services_status).seconds

            if real_reload_time >= self.reload_time:
                _get_services()
        else:
            _get_services()

        return self.services


app = ProxyApp(__name__)
app.config.from_envvar('INDEXER_PROXY_SETTINGS')

if not app.debug:
    my_handler = logs.IndexerProxyHandler(
        app.config.get("INFO_LOGNAME"),
        app.config.get("EXC_LOGNAME"))
    formatter = logging.Formatter(logs.FILE_FORMAT, logs.TIME_FORMAT)
    my_handler.setFormatter(formatter)

    logging.getLogger('').addHandler(my_handler)
    logging.getLogger('').setLevel(logging.INFO)


def close_app_sock():
    if app.sock:
        app.sock.close()
    return None


@app.route('/ping')
def ping():
    return "Ok\n"


@app.errorhandler(errors.HttpIndexerProxyError)
def special_http_exception_handler(error):
    app.logger.error('%s', error, exc_info=sys.exc_info())
    app.logger.error('environ: %s', pprint.pformat(flask.request.environ))
    app.logger.error('request: %s', pprint.pformat(flask.request.values.to_dict()))

    response = flask.render_template_string(
            ERROR_TEMPLATE,
            status_message=error.status_message,
            message=error.message)

    return flask.make_response(flask.Response(
        response=response,
        status=error.status_code,
        content_type='text/html; charset=utf-8',
    ))


@app.route('/service/<service_name>', methods=['POST'])
def send_indexer_message(service_name):
    content_type = flask.request.headers.get('content-type')
    if not CONTENT_TYPE_RE.match(content_type):
        raise errors.HttpIndexerProxyError(
            415,
            '415 Unsupported Media Type',
            'Content-Type must be "multipart/form-data;".')

    if service_name is None or service_name not in app.get_services(app.config.get('SERVICES_FILENAME')).keys():
        raise errors.HttpIndexerProxyError(
            400,
            '400 Bad request',
            'Service name %s has invalid value.' % service_name)
    service_name = app.get_services(app.config.get('SERVICES_FILENAME')).get(service_name)

    try:
        json_message = flask.request.form.get('json_message')
        if not json_message:
            raise errors.HttpIndexerProxyError(
                400,
                '400 Bad request',
                'Field json_message required, can not be empty.')
        json_message = json.loads(json_message)
    except ValueError:
        raise errors.HttpIndexerProxyError(
            400,
            '400 Bad request',
            'Send bad field json_message: %r.' % json_message)

    document_file = flask.request.files.get('document')
    document_form = flask.request.form.get('document')

    if (document_form is None and document_file is None) or (document_form is not None and document_file is not None):
        raise errors.HttpIndexerProxyError(
            400,
            '400 Bad request',
            'Sould be one of file-field or data-filed with name="document".')

    if document_file is not None:
        document = document_file.read()

    if document_form is not None:
        document = document_form

    m = utils.parse_data(json_message, document)
    try:
        proto_message = m.get_dicpatcher_proto_message(
            service_name,
            with_response=True)
    except errors.ProtobufError, e:
        raise errors.HttpIndexerProxyError(400, '400 Bad request', e)

    for _ in xrange(app.config.get('RETRY_COUNT')):
        try:
            if not app.sock:
                app.sock = utils.get_sock(
                    app.config.get('INDEXER_HOST'),
                    app.config.get('INDEXER_PORT'),
                    app.config.get('INDEXER_TIMEOUT'),
                    app.config.get('INDEXER_RETRY_COUNT_CONNECT'))
            app.logger.info('Send message for service=%s: %s', service_name, m)
            utils.send_message(app.sock, proto_message, with_response=True)
            break
        except (errors.FailedToConnectDispatcher, errors.ClosedConnection), e:
            app.logger.warning(e)
            app.sock = close_app_sock()
        except (socket.error, socket.gaierror), e:
            app.logger.warning('Error in socket, where send/get data %s: %s', m, e, exc_info=sys.exc_info())
            app.sock = close_app_sock()
        except errors.IncorrectDocumentError, e:
            # расскомментировать для нагрузочного тестирования
            #~ break
            app.logger.warning('Rtyserver send status=INCORRECT_DOCUMENT, where indexing message %s: %s', m, e, exc_info=sys.exc_info())
            raise errors.HttpIndexerProxyError(
                400,
                '400 Bad request',
                'Bad sending message: message does not contain any document'
                ' or the document contains an invalid'
                ' or does not keyprefix or bad service_name.')
        except errors.InternalError, e:
            app.logger.warning('Rtyserver send status=INTERNAL_ERROR, where indexing message %s: %s', m, e, exc_info=sys.exc_info())
            raise errors.HttpIndexerProxyError(
                502,
                '502 Bad Gateway',
                'Message has not been indexed, so that there was an internal error on the rtyserver.')
        except errors.IndexerError, e:
            app.logger.warning('Exception where indexing message %s: %s', m, e, exc_info=sys.exc_info())
        except Exception, e:
            app.logger.error('Some exception in message %s: %s', m, e, exc_info=sys.exc_info())
            app.sock = close_app_sock()
            raise errors.HttpIndexerProxyError(
                502,
                '502 Bad Gateway',
                'Failed send message to indexing, because we have error from rtyserver.')
    else:
        app.sock = close_app_sock()
        raise errors.HttpIndexerProxyError(
            502,
            '502 Bad Gateway',
            'Failed send message to indexing, because we have error from rtyserver.')

    return flask.make_response(flask.render_template_string(OK_TEMPLATE))


if __name__ == '__main__':
    app.run()
    # for profile application
    # run python indexer_proxy.py
    #~ import cProfile
    #~ cProfile.run("app.run(host='127.0.0.1', port=5685)", "profile.log")
