# coding: utf-8
from copy import deepcopy
from datetime import timedelta
from functools import wraps, update_wrapper
import logging
import time
import re

import flask
from flask import make_response, request, current_app

from lxml import etree as ET
from .xml import EM

from werkzeug.exceptions import HTTPException

import six
if six.PY3:
    unicode = str


class LongRequestsProfile(object):

    def __init__(self, app, warn_duration, error_duration):
        self.app = app
        self.warn_duration = warn_duration
        self.error_duration = error_duration
        self.logger = logging.getLogger("profiler")

    def __call__(self, env, start_response):
        start = time.time()
        try:
            return self.app(env, start_response)
        finally:
            finish = time.time()
            duration = finish - start
            url = env['werkzeug.request'].url
            if duration >= self.error_duration:
                self.logger.error("Too long request execution: %s %.3f sec" %
                                    (url, duration))
            elif duration >= self.warn_duration:
                self.logger.warn("Long request execution: %s %.3f sec" %
                                    (url, duration))


def serialize_et(func):

    @wraps(func)
    def wrapper(*args, **kw):
        try:
            result = func(*args, **kw)
            if isinstance(result, ET._Element) or isinstance(result, ET._ElementTree):
                res = ET.tostring(result, encoding='utf-8', pretty_print=True,
                                  xml_declaration=True)
                resp = make_response(res)
                resp.mimetype = "text/xml"
                return resp
            else:
                return result
        except HTTPException:
            raise
        except Exception as ex:
            logging.exception("Exception till processing request header=%r form=%r args=%r" %
                                (request.headers, request.form, request.args))
            msg = ET.tostring(EM.error(unicode(ex)), encoding='utf-8', pretty_print=True,
                              xml_declaration=True)
            resp = make_response(msg, 500)
            resp.content_type = 'text/xml'
            return resp
    return wrapper


def xml_response(xml, code=200):
    res = ET.tostring(xml, encoding='utf-8', xml_declaration=True)
    resp = flask.make_response(res, code)
    resp.mimetype = "text/xml"
    return resp


class ServiceException(Exception):
    def __init__(self, message, status):
        self.message = message
        self.status = status
        super(ServiceException, self).__init__(message, status)


def error_handler(ex, root_element):
    root_element = deepcopy(root_element)
    if isinstance(ex, HTTPException):
        logging.warn('HTTP error (code: %s, description: %s) while processing request '
                     'header=%r form=%r args=%r',
                     ex.code, ex.description, request.headers, request.form, request.args)
        http_code = ex.code
        error_el = ET.SubElement(root_element, 'error')
        error_el.text = unicode(ex.description)
    elif isinstance(ex, ServiceException):
        logging.warning('ServiceException while processing request '
                        'header=%r form=%r args=%r',
                        request.headers, request.form, request.args, exc_info=True)
        http_code = 200
        error_el = ET.SubElement(root_element, 'error')
        error_el.text = ex.message
        error_el.attrib['status'] = ex.status
    else:
        logging.exception('Exception while processing request '
                          'header=%r form=%r args=%r',
                          request.headers, request.form, request.args)
        http_code = 500
        error_el = ET.SubElement(root_element, 'error')
        error_el.text = unicode(ex)
        error_el.attrib['status'] = 'INTERNAL_ERROR'

    return xml_response(root_element, http_code)


def correct_page(page, per_page, length):
    page = int(page)
    per_page = int(per_page)
    if per_page <= 0:
        per_page = 10

    total_pages, remainder = divmod(length, per_page)
    if remainder > 0 or total_pages == 0:
        total_pages += 1

    if page < 1:
        return 1
    elif page > total_pages:
        return total_pages
    else:
        return page


def token_from_request():
    return request.values.get('token')


# snippet from http://flask.pocoo.org/snippets/56/
def crossdomain(origin_regex=None, methods=None, headers=None,
                max_age=None, attach_to_all=True,
                automatic_options=True):
    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))
    if headers is not None and not isinstance(headers, str):
        headers = ', '.join(x.upper() for x in headers)
    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        if methods is not None:
            return methods

        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
                resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            if 'Origin' in request.headers:
                origin_header = request.headers['Origin']
                if origin_regex is not None and re.match(origin_regex, origin_header):
                    h['Access-Control-Allow-Origin'] = origin_header

            h['Access-Control-Allow-Methods'] = get_methods()
            if max_age is not None:
                h['Access-Control-Max-Age'] = str(max_age)
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            return resp

        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)
    return decorator
