import os
import time

import tornado.concurrent
import tornado.web

from . import uimodules
from .compat import ThreadPoolExecutor
from .compat import sleep


BASE_DIR = os.path.dirname(os.path.realpath(__file__))
STATIC_DIR = os.path.join(BASE_DIR, "statics")


class SuccessHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        self.write("OK")


class NestedHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]
        with tracer.trace("tornado.sleep"):
            yield sleep(0.05)
        self.write("OK")


class NestedWrapHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        # define a wrapped coroutine: having an inner coroutine
        # is only for easy testing
        @tracer.wrap("tornado.coro")
        @tornado.gen.coroutine
        def coro():
            yield sleep(0.05)

        yield coro()
        self.write("OK")


class NestedExceptionWrapHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        # define a wrapped coroutine: having an inner coroutine
        # is only for easy testing
        @tracer.wrap("tornado.coro")
        @tornado.gen.coroutine
        def coro():
            yield sleep(0.05)
            raise Exception("Ouch!")

        yield coro()
        self.write("OK")


class ExceptionHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        raise Exception("Ouch!")


class HTTPExceptionHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        raise tornado.web.HTTPError(status_code=501, log_message="unavailable", reason="Not Implemented")


class HTTPException500Handler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        raise tornado.web.HTTPError(status_code=500, log_message="server error", reason="Server Error")


class TemplateHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        self.render("templates/page.html", name="home")


class TemplatePartialHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        self.render("templates/list.html", items=["python", "go", "ruby"])


class TemplateExceptionHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        self.render("templates/exception.html")


class SyncSuccessHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("OK")


class SyncExceptionHandler(tornado.web.RequestHandler):
    def get(self):
        raise Exception("Ouch!")


class SyncNestedWrapHandler(tornado.web.RequestHandler):
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        # define a wrapped coroutine: having an inner coroutine
        # is only for easy testing
        @tracer.wrap("tornado.func")
        def func():
            time.sleep(0.05)

        func()
        self.write("OK")


class SyncNestedExceptionWrapHandler(tornado.web.RequestHandler):
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        # define a wrapped coroutine: having an inner coroutine
        # is only for easy testing
        @tracer.wrap("tornado.func")
        def func():
            time.sleep(0.05)
            raise Exception("Ouch!")

        func()
        self.write("OK")


class CustomDefaultHandler(tornado.web.ErrorHandler):
    """
    Default handler that is used in case of 404 error; in our tests
    it's used only if defined in the get_app() function.
    """

    pass


class ExecutorHandler(tornado.web.RequestHandler):
    # used automatically by the @run_on_executor decorator
    executor = ThreadPoolExecutor(max_workers=3)

    @tornado.concurrent.run_on_executor
    def outer_executor(self):
        tracer = self.settings["datadog_trace"]["tracer"]
        with tracer.trace("tornado.executor.with"):
            time.sleep(0.05)

    @tornado.gen.coroutine
    def get(self):
        yield self.outer_executor()
        self.write("OK")


class ExecutorSubmitHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(max_workers=3)

    def query(self):
        tracer = self.settings["datadog_trace"]["tracer"]
        with tracer.trace("tornado.executor.query"):
            time.sleep(0.05)

    @tornado.gen.coroutine
    def get(self):
        # run the query in another Executor, without using
        # Tornado decorators
        yield self.executor.submit(self.query)
        self.write("OK")


class ExecutorDelayedHandler(tornado.web.RequestHandler):
    # used automatically by the @run_on_executor decorator
    executor = ThreadPoolExecutor(max_workers=3)

    @tornado.concurrent.run_on_executor
    def outer_executor(self):
        # waiting here means expecting that the `get()` flushes
        # the request trace
        time.sleep(0.01)
        tracer = self.settings["datadog_trace"]["tracer"]
        with tracer.trace("tornado.executor.with"):
            time.sleep(0.05)

    @tornado.gen.coroutine
    def get(self):
        # we don't yield here but we expect that the outer_executor
        # has the right parent; tests that use this handler, must
        # yield sleep() to wait thread execution
        self.outer_executor()
        self.write("OK")


try:

    class ExecutorCustomHandler(tornado.web.RequestHandler):
        # not used automatically, a kwarg is required
        custom_thread_pool = ThreadPoolExecutor(max_workers=3)

        @tornado.concurrent.run_on_executor(executor="custom_thread_pool")
        def outer_executor(self):
            # wait before creating a trace so that we're sure
            # the `tornado.executor.with` span has the right
            # parent
            tracer = self.settings["datadog_trace"]["tracer"]
            with tracer.trace("tornado.executor.with"):
                time.sleep(0.05)

        @tornado.gen.coroutine
        def get(self):
            yield self.outer_executor()
            self.write("OK")


except TypeError:
    # the class definition fails because Tornado 4.0 and 4.1 don't support
    # `run_on_executor` with params. Because it's just this case, we can
    # use a try-except block, but if we have another case we may move
    # these endpoints outside the module and use a compatibility system
    class ExecutorCustomHandler(tornado.web.RequestHandler):
        pass


class ExecutorCustomArgsHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        # this is not a legit use of the decorator so a failure is expected
        @tornado.concurrent.run_on_executor(object(), executor="_pool")
        def outer_executor(self):
            pass

        yield outer_executor(self)
        self.write("OK")


class ExecutorExceptionHandler(tornado.web.RequestHandler):
    # used automatically by the @run_on_executor decorator
    executor = ThreadPoolExecutor(max_workers=3)

    @tornado.concurrent.run_on_executor
    def outer_executor(self):
        # wait before creating a trace so that we're sure
        # the `tornado.executor.with` span has the right
        # parent
        time.sleep(0.05)
        tracer = self.settings["datadog_trace"]["tracer"]
        with tracer.trace("tornado.executor.with"):
            raise Exception("Ouch!")

    @tornado.gen.coroutine
    def get(self):
        yield self.outer_executor()
        self.write("OK")


class ExecutorWrapHandler(tornado.web.RequestHandler):
    # used automatically by the @run_on_executor decorator
    executor = ThreadPoolExecutor(max_workers=3)

    @tornado.gen.coroutine
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        @tracer.wrap("tornado.executor.wrap")
        @tornado.concurrent.run_on_executor
        def outer_executor(self):
            time.sleep(0.05)

        yield outer_executor(self)
        self.write("OK")


class ExecutorExceptionWrapHandler(tornado.web.RequestHandler):
    # used automatically by the @run_on_executor decorator
    executor = ThreadPoolExecutor(max_workers=3)

    @tornado.gen.coroutine
    def get(self):
        tracer = self.settings["datadog_trace"]["tracer"]

        @tracer.wrap("tornado.executor.wrap")
        @tornado.concurrent.run_on_executor
        def outer_executor(self):
            time.sleep(0.05)
            raise Exception("Ouch!")

        yield outer_executor(self)
        self.write("OK")


def make_app(settings={}):
    """
    Create a Tornado web application, useful to test
    different behaviors.
    """
    settings["ui_modules"] = uimodules

    return tornado.web.Application(
        [
            # custom handlers
            (r"/success/", SuccessHandler),
            (r"/nested/", NestedHandler),
            (r"/nested_wrap/", NestedWrapHandler),
            (r"/nested_exception_wrap/", NestedExceptionWrapHandler),
            (r"/exception/", ExceptionHandler),
            (r"/http_exception/", HTTPExceptionHandler),
            (r"/http_exception_500/", HTTPException500Handler),
            (r"/template/", TemplateHandler),
            (r"/template_partial/", TemplatePartialHandler),
            (r"/template_exception/", TemplateExceptionHandler),
            # handlers that spawn new threads
            (r"/executor_handler/", ExecutorHandler),
            (r"/executor_submit_handler/", ExecutorSubmitHandler),
            (r"/executor_delayed_handler/", ExecutorDelayedHandler),
            (r"/executor_custom_handler/", ExecutorCustomHandler),
            (r"/executor_custom_args_handler/", ExecutorCustomArgsHandler),
            (r"/executor_exception/", ExecutorExceptionHandler),
            (r"/executor_wrap_handler/", ExecutorWrapHandler),
            (r"/executor_wrap_exception/", ExecutorExceptionWrapHandler),
            # built-in handlers
            (r"/redirect/", tornado.web.RedirectHandler, {"url": "/success/"}),
            (r"/statics/(.*)", tornado.web.StaticFileHandler, {"path": STATIC_DIR}),
            # synchronous handlers
            (r"/sync_success/", SyncSuccessHandler),
            (r"/sync_exception/", SyncExceptionHandler),
            (r"/sync_nested_wrap/", SyncNestedWrapHandler),
            (r"/sync_nested_exception_wrap/", SyncNestedExceptionWrapHandler),
        ],
        **settings
    )
