# encoding=utf-8

import os
import code
import logging
import traceback
import signal

import sqlalchemy
from flask_babelex import gettext
from flask_migrate import Migrate, upgrade

from psi import MIGRATION_DIR
from psi.app import const
from psi.app.service import Info


__version__ = '0.6.8'
log = logging.getLogger(__name__)


try:
    from psycopg2cffi import compat
    compat.register()
except:
    pass


def debug(sig, frame):
    """Interrupt running process, and provide a python prompt for
    interactive debugging."""
    d={'_frame':frame}         # Allow access to frame object.
    d.update(frame.f_globals)  # Unless shadowed by global
    d.update(frame.f_locals)

    i = code.InteractiveConsole(d)
    message = "Signal received : entering python shell.\nTraceback:\n"
    message += ''.join(traceback.format_stack(frame))
    i.interact(message)


def listen():
    signal.signal(signal.SIGUSR1, debug)  # Register handler


listen()


def create_app(custom_config=None):
    from flask import Flask

    flask_app = Flask(__name__, template_folder='../templates', static_folder='../static')

    if custom_config is not None:
        active_config = custom_config
    else:
        import psi.app.config as default_config
        if os.environ.get('DEBUG') == 'True':
            active_config = default_config.DevConfig
        else:
            active_config = default_config.ProductionConfig
    active_config.VERSION = __version__
    flask_app.config.from_object(active_config)

    return flask_app


def init_flask_security(flask_app, database):
    from flask_security import SQLAlchemyUserDatastore, Security
    from psi.app.models.user import User
    from psi.app.models.role import Role
    import psi.app.config as config
    for key, value in config.BaseConfig.security_messages.items():
        flask_app.config['SECURITY_MSG_' + key] = value
    user_datastore = SQLAlchemyUserDatastore(database, User, Role)
    from psi.app.views.login_form import LoginForm
    security = Security(flask_app, user_datastore, login_form=LoginForm)
    return security


def init_admin_views(flask_app, database):
    from psi.app.views import init_admin_views
    try:
        return init_admin_views(flask_app, database)
    except sqlalchemy.exc.SQLAlchemyError as e:
        # If we're running the flask utility script and Postgres
        # isn't available, `init_admin_views` will raise an OperationalError,
        # blocking the utility script from running. Instead, we catch the
        # exception and warn the user so the user can invoke some of the
        # commands that don't require database connections.
        # TODO: don't require Postgres on app object initialization
        log.exception(e)
        log.warn('Cannot register admin views because of a SQLAlchemy error. '
                 'Skipping...')


def init_db(flask_app):
    from flask_sqlalchemy import SQLAlchemy
    sqlalchemy = SQLAlchemy(flask_app, session_options={'autoflush': False})
    sqlalchemy.init_app(flask_app)
    Info.set_db(sqlalchemy)
    return sqlalchemy


def init_migrate(flask_app, database):
    Migrate(app=flask_app, db=database, directory=MIGRATION_DIR)


def init_babel(flask_app):
    from flask_babelex import Babel
    # return Babel(default_locale='zh_Hans_CN', app=flask_app)
    return Babel(app=flask_app)


def init_logging(flask_app):
    from raven.contrib.flask import Sentry
    import logging
    from logging import FileHandler
    from logging import Formatter
    logger = logging.getLogger('psi')
    if flask_app.config['DEBUG']:
        # set environment variable WERKZEUG_DEBUG_PIN to off to
        # disable debug PIN for werkzeug.
        # log = logging.getLogger('werkzeug')
        # log.setLevel(logging.INFO)
        # file_handler = FileHandler('betterlife-psi.log', encoding='UTF-8', mode='w')
        # file_handler.setFormatter(Formatter(const.FILE_HANDLER_LOG_FORMAT))
        # logger.addHandler(file_handler)
        #logger.setLevel(logging.DEBUG)
        pass
    else:
        handler = logging.StreamHandler()
        handler.setFormatter(Formatter(const.CONSOLE_HANDLER_LOG_FORMAT))
        logger.addHandler(handler)
        logger.setLevel(logging.INFO)
        Sentry(flask_app, logging=True, level=logging.INFO)


def init_debug_toolbar(flask_app):
    from flask_debugtoolbar import DebugToolbarExtension
    if flask_app.config['DEBUG']:
        flask_app.debug = True
        DebugToolbarExtension(flask_app)


def define_route_context(flask_app, db, babel):
    from werkzeug.utils import redirect
    from flask_login import current_user
    from flask import request, current_app

    @flask_app.route('/')
    def hello():
        return redirect("/admin", code=302)

    @flask_app.teardown_appcontext
    def shutdown_session(exception=None):
        db.session.remove()

    @babel.timezoneselector
    def get_timezone():
        if getattr(current_user, 'timezone', None) is not None:
            return current_user.timezone.code
        return 'CST' if current_app.config['DEBUG'] else "UTC"

    @babel.localeselector
    def get_locale():
        """
        Put your logic here. Application can store locale in
        user profile, cookie, session, etc.
        This is the setting actually take effective
        """
        try:
            if getattr(current_user, 'locale', None) is not None:
                return current_user.locale.code
            return 'zh_CN' if current_app.config['DEBUG'] else \
                   request.accept_languages.best_match(['zh_CN', 'en_US'])
        except BaseException:
            return 'zh_CN' if current_app.config['DEBUG'] else \
                   request.accept_languages.best_match(['zh_CN', 'en_US'])


def init_https(app):
    # only trigger SSLify if the app is running on Heroku and debug is false
    if (app.config['DEBUG'] is False) and ('DYNO' in os.environ):
        from flask_sslify import SSLify
        SSLify(app)


def init_jinja2_functions(app):
    from psi.app.utils.ui_util import render_version, has_detail_field, \
        is_inline_field, is_list_field
    app.add_template_global(render_version, 'render_version')
    app.add_template_global(has_detail_field, 'has_detail_field')
    app.add_template_global(is_inline_field, 'is_inline_field')
    app.add_template_global(is_list_field, 'is_list_field')
    app.add_template_global(gettext, 'mytext')


def init_image_service(app):
    """
    Initialize image store service
    """
    image_store = app.config['IMAGE_STORE_SERVICE']
    if image_store is not None:
        Info.set_image_store_service(image_store(app))


def init_flask_restful(app):
    """
    Initialize flask restful api
    """
    from flask_restful import Api
    from psi.app.api import init_all_apis
    flask_restful = Api(app)
    init_all_apis(flask_restful)
    return flask_restful


def init_reports(app, api):
    """
    Init reports
    """
    from psi.app.reports import init_report_endpoint
    init_report_endpoint(app, api)


def init_socket_io(app):
    from flask_socketio import SocketIO
    from psi.app.socketio import init_socket_tio_handlers
    socket_io = SocketIO(app)
    init_socket_tio_handlers(socket_io)
    return socket_io


def init_all(app, migrate=True):
    init_logging(app)
    # === Important notice to the maintainer ===
    # This line was use to database = init_db(app)
    # But we found session can not be cleaned among different
    # Unit tests, so we add this to avoid issue
    # sqlalchemy-object-already-attached-to-session
    # http://stackoverflow.com/questions/24291933/sqlalchemy-object-already-attached-to-session
    # A similar issue was captured on
    # https://github.com/jarus/flask-testing/issues/32
    # Please don't try to modify the follow four lines.
    # Please don't try to modify the follow four lines.
    # Please don't try to modify the follow four lines.
    if Info.get_db() is None:
        database = init_db(app)
    else:
        database = Info.get_db()
        database.init_app(app)
    security = init_flask_security(app, database)
    init_migrate(app, database)
    if migrate:
        with app.app_context():
            upgrade(directory=MIGRATION_DIR)

    init_https(app)
    init_admin_views(app, database)
    babel = init_babel(app)
    api = init_flask_restful(app)
    init_reports(app, api)
    init_jinja2_functions(app)
    # init_debug_toolbar(app)
    init_image_service(app)
    socket_io = init_socket_io(app)
    define_route_context(app, database, babel)

    # define a context processor for merging flask-admin's template context
    # into the flask-security views.
    @security.context_processor
    def security_context_processor():
        from flask import url_for
        return dict(
            get_url=url_for
        )

    @app.teardown_appcontext
    def shutdown_session(exception=None):
        database = Info.get_db()
        database.session.remove()

    return socket_io