# -*- coding: utf-8 -*-


import tornado.web
#import tornado.ioloop
#import tornado.gen
#import tornado.concurrent
import tornado.options

import inspect
import importlib

from .response import Response as dpResponse
from .engine import Engine as dpEngine
from .model import InValueModelConfig as dpInValueModelConfig


class Handler(tornado.web.RequestHandler, dpEngine):
    def __init__(self, application, request, **kwargs):
        super(Handler, self).__init__(application, request, **kwargs)

        self.prefix = self.prefix
        self.parent = None
        self.routed = None
        self.blocked = False
        self.interrupted = False
        self.handlers = []

        session_dsn = tornado.options.options.session_dsn

        self._sessionid = None
        self._sessiondb = session_dsn or dpInValueModelConfig(driver='sqlite', database='session')

    def initialize(self, prefix=None):
        self.prefix = prefix

    @property
    def executor(self):
        return self._executor

    @staticmethod
    def capitalized_method_name(method_name):
        s = method_name.split('_')
        x = map(lambda p: p.capitalize(), s)

        return ''.join(x)

    #@tornado.concurrent.run_on_executor
    def route(self, method, path, initialize=None):
        return self._route(method, path, initialize)

    def _route(self, method, path, initialize=None):
        if self.interrupted:
            self.on_interrupt()
            return False

        if self.blocked:
            self.finish_with_error(404, 'Page Not Found')
            return False

        temp_paths = {}
        paths = []

        for e in (path or '').split('/'):
            if e.find('.') != -1:
                uniqid = self.helper.random.uuid()
                paths.append(uniqid)
                temp_paths[uniqid] = e
            else:
                paths.append(e)

        paths_req = path.split('/') if path else None
        path = '/'.join(paths)

        module_path = '%s%s' % (self.prefix, '.%s' % path.replace('/', '.') if path else '')
        module_paths = str.split(self.helper.string.to_str(module_path), '.')
        parameters = []
        previous = None

        if module_paths[-1].strip() == '':
            module_paths.pop()

        while True:
            if len(module_paths) == 0:
                break

            module_path = '.'.join(module_paths)
            pop = module_paths.pop()
            controller_name = self.capitalized_method_name(pop) if module_path != self.prefix else 'Starter'
            class_name = '%sController' % controller_name
            handler_module = None

            try:
                handler_module = importlib.import_module(module_path)
                handler = getattr(handler_module, class_name)
                handler = handler(self.application, self.request, prefix=self.prefix, parent=self)
                handler.prefix = self.prefix
                handler.parent = self

            except (KeyError, ValueError, AttributeError, ImportError):
                try:
                    paths_req.pop()

                    class_name = '%sController' % (self.capitalized_method_name(previous))
                    handler = getattr(handler_module, class_name)
                    handler = handler(self.application, self.request, prefix=self.prefix, parent=self)

                    # Its handler.
                    parameters.pop()

                except AttributeError:
                    previous = pop
                    parameters.append(pop)
                    continue

            self.handlers.append(handler)

            on_prepare = getattr(handler, 'on_prepare', None)
            on_prepare_sepc = inspect.getargspec(on_prepare) if on_prepare else None
            on_prepare = (on_prepare() if len(on_prepare_sepc.args) == 1 else on_prepare(True if initialize else False)) \
                if on_prepare else None

            if on_prepare is False:
                on_interrupt = getattr(handler, 'on_interrupt', None)

                if not on_interrupt:
                    self.finish_with_error(500, 'An error has occurred')
                else:
                    try:
                        on_interrupt()
                    except Exception as e:
                        self.logging.error(e)
                        self.finish_with_error(500, 'An error has occurred')

                return handler

            elif on_prepare is not True and paths_req:
                paths_req.pop()
                paths_req = '/'.join(paths_req)

                if initialize is not False:
                    routed = self._route(method, paths_req, True if paths_req else False)

                    if isinstance(routed, dpEngine):
                        self.postprocess(routed)
                        return False

            if initialize is not None:
                return False

            method = getattr(handler, method, None)

            if not method:
                self.finish_with_error(404, 'Page Not Found (No Method Implemented)')

            spec = inspect.getargspec(method)
            req_param_count = len(spec.args) - 1
            def_param_count = len(spec.defaults) if spec.defaults else 0

            if not spec.varargs \
                    and (len(parameters) > req_param_count or req_param_count > len(parameters) + def_param_count):
                self.finish_with_error(404, 'Page Not Found (Parameters are mismatched)')

                return False

            parameters.reverse()

            try:
                method(*[temp_paths[x] if x in temp_paths else x for x in parameters])
                return handler

            except tornado.web.HTTPError as e:
                raise e

            except dpResponse as e:
                self.set_status(e.http_status_code)
                self.finish(e.response())

                return handler

            except Exception as e:
                self.logger.exception(e)
                self.finish_with_error(500)

                return False

        self.finish_with_error(404, 'Page Not Found')

        return False

    def route_index(self):
        try:
            index_method = getattr(self, 'index')
        except AttributeError:
            raise tornado.web.HTTPError(404)

        try:
            return index_method()
        except Exception:
            raise tornado.web.HTTPError(500)

    @staticmethod
    def finish_with_error(status_code, message='An error has occurred'):
        raise tornado.web.HTTPError(status_code, reason=message)

    def write_error(self, status_code, **kwargs):
        error = kwargs.get('exc_info', None)
        error = error[1] if error else None
        reason = error.reason if error and getattr(error, 'reason', None) else 'An error has occurred'

        self.set_status(status_code)

        for handler in self.handlers:
            on_error = getattr(handler, 'on_error', None)

            if on_error:
                on_error(status_code, reason)
                return self.postprocess(handler)

        if status_code == 404:
            return self.render('system/http/pp_404.html')

        return self.render('system/http/pp_5xx.html')

    def head(self, path=None):
        self.routed = True
        self.__processor('head', path)

    def get(self, path=None):
        self.routed = True
        self.__processor('get', path)

    def post(self, path=None):
        self.routed = True
        self.__processor('post', path)

    def delete(self, path=None):
        self.routed = True
        self.__processor('delete', path)

    def patch(self, path=None):
        self.routed = True
        self.__processor('patch', path)

    def put(self, path=None):
        self.routed = True
        self.__processor('put', path)

    def postprocess(self, x):
        if self._headers_written:
            return

        if not x:
            return
        elif x._render:
            self.__render(x)
        elif x._finish:
            self.__finish(x)
        elif x._write:
            self.__write(x)

        for handler in self.handlers:
            on_finish = getattr(handler, 'on_finish', None)

            if on_finish:
                if on_finish(self.get_status()) is True:
                    break

    #@tornado.web.asynchronous
    #@tornado.gen.coroutine
    def __processor(self, method, path):
        #x = yield self.route(method, path)
        x = self.route(method, path)
        self.postprocess(x)

    def __render(self, x):
        if not x._render:
            return

        t = x._render['t']
        k = x._render['k']

        if not k:
            self.render(t)
        else:
            self.render(t, **k)

    def __write(self, x):
        if not x._write:
            return

        for s in x._write:
            self.write(s)

    def __finish(self, x):
        if not x._finish:
            return

        self.finish(x._finish)

    @property
    def remote_ip(self):
        if 'X-Forwarded-For' in self.request.headers:
            x = self.request.headers['X-Forwarded-For']
            x = x.split(',') if x else None

            return x[0] if x else None

        else:
            return self.request.remote_ip

    def set_secure_cookie(self, name, value, expires_days=30, version=2, **kwargs):
        secure_cookie = self.helper.crypto.encrypt(
            value,
            True,
            0,
            self.request.headers["User-Agent"] if "User-Agent" in self.request.headers else 'unknown')

        return super(Handler, self).set_secure_cookie(name, secure_cookie, expires_days, version, **kwargs)

    def get_secure_cookie(self, name, value=None, max_age_days=31, min_version=None):
        secure_cookie = super(Handler, self).get_secure_cookie(name, value, max_age_days, min_version)

        if secure_cookie:
            try:
                secure_cookie = self.helper.crypto.decrypt(
                    secure_cookie,
                    self.request.headers["User-Agent"] if "User-Agent" in self.request.headers else 'unknown')
            except:
                secure_cookie = None

            return secure_cookie if secure_cookie else None

        else:
            return None

    def get_sessionid(self):
        return self._sessionid if self._sessionid else self.set_sessionid()

    def set_sessionid(self, sessionid=None):
        if not sessionid:
            try:
                sessionid_from_cookie = self.get_secure_cookie('PSESSIONID')
                sessionid = self.helper.string.to_str(sessionid_from_cookie)

                if not sessionid.isalnum():
                    sessionid = None

            except:
                sessionid = None

        sessionid = sessionid or self.helper.crypto.sha224_hash(self.helper.datetime.current_time_millis())
        self.set_secure_cookie('PSESSIONID', sessionid)
        self._sessionid = sessionid

        return sessionid

    def get_session_value(self, name, expire_in=None, sessionid=None):
        sessionid = self.get_sessionid() if not sessionid else sessionid
        key = '%s:%s' % (sessionid, name)

        return self.cache.get(key, self._sessiondb, expire_in=expire_in)

    def get_session_value_ttl(self, name):
        sessionid = self.get_sessionid()
        key = '%s:%s' % (sessionid, name)

        return self.cache.ttl(key, self._sessiondb)

    @property
    def session_default_expire_in(self):
        return tornado.options.options.session_exp_in or 7200

    def set_session_value(self, name, value, expire_in=None):
        sessionid = self.get_sessionid()
        key = '%s:%s' % (sessionid, name)
        expire_in = expire_in if expire_in is not None else self.session_default_expire_in

        return self.cache.set(key, value, self._sessiondb, expire_in=expire_in)

    def session(self, name, value=None, expire_in=None):
        expire_in = expire_in if expire_in is not None else self.session_default_expire_in

        if value is not None:
            return self.set_session_value(name, value, expire_in=expire_in)

        else:
            return self.get_session_value(name)

    def redirect(self, url, permanent=False, status=None):
        try:
            super(Handler, self).redirect(url, permanent, status)
        except (AttributeError, TypeError):
            pass

    def get_user_agent(self, parsed=True):
        if not parsed:
            return self.request.headers['User-Agent'] if 'User-Agent' in self.request.headers else ''

        else:
            from .plugin import http_agent_parser

            ua = self.get_user_agent(False)
            ua = http_agent_parser.detect(ua)

            p_name = ua['platform']['name'] if 'platform' in ua and 'name' in ua['platform'] else 'Unknown'
            p_version = ua['platform']['version'] if 'platform' in ua and 'version' in ua['platform'] else 'Unknown'

            try:
                p_version_major = int(float(p_version.split('.')[0])) if p_version else 0
            except ValueError:
                p_version_major = 0

            platform = '_p-%s-%s' % (p_name, p_version)
            platform = platform.lower().replace(' ', '-').replace('.', '-')

            platform_major = '_p-%s-%s' % (p_name, p_version_major)
            platform_major = platform_major.lower().replace(' ', '-').replace('.', '-')

            os_name = ua['os']['name'] if 'os' in ua and 'name' in ua['os'] else 'Unknown'
            os_version = ua['os']['version'] if 'os' in ua and 'version' in ua['os'] else 'Unknown'

            os = '_o-%s-%s' % (os_name, os_version)
            os = os.lower().replace(' ', '-').replace('.', '-')

            os_name = '_o-%s' % os_name
            os_name = os_name.lower().replace(' ', '-').replace('.', '-')

            browser_name = ua['browser']['name'] if 'browser' in ua and 'name' in ua['browser'] else 'Unknown'
            browser_version = ua['browser']['version'] if 'browser' in ua and 'version' in ua['browser'] else 'Unknown'

            try:
                browser_version_major = int(float(browser_version.split('.')[0]))
            except ValueError:
                browser_version_major = 0

            browser = '_b-%s-%s' % (browser_name, browser_version)
            browser = browser.lower().replace(' ', '-').replace('.', '-')

            browser_major = '_b-%s-%s' % (browser_name, browser_version_major)
            browser_major = browser_major.lower().replace(' ', '-').replace('.', '-')

            browser_type = '_b-%s' % browser_name
            browser_type = browser_type.lower().replace(' ', '-').replace('.', '-')

            ua['platform_str'] = platform
            ua['platform_major_str'] = platform_major
            ua['os_str'] = os
            ua['os_name_str'] = os_name
            ua['browser_str'] = browser
            ua['browser_major_str'] = browser_major
            ua['browser_type_str'] = browser_type

            return ua

    def on_interrupt(self):
        pass
