from flask import Flask, request, g, session
import flask.logging
import flask.wrappers
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
import locale
import logging
import os
import tempfile
from typing import Optional
import werkzeug.exceptions
import werkzeug.formparser

import mo
import mo.config as config
import mo.db as db
import mo.ext.assets
import mo.jobs
import mo.rights
import mo.users
import mo.util


# Ohýbáme Flask, aby uploadované soubory ukládal do adresáře podle našeho přání,
# aby se pak daly zařadit mezi datové soubory prostým hardlinkováním. Za tímto účelem
# subclassujeme Request, aby použil subclassovaný FormDataParser, který použije naši
# stream factory místo defaultní.

def mo_stream_factory(total_content_length, filename, content_type, content_length=None):
    return tempfile.NamedTemporaryFile(dir=mo.util.data_dir('tmp'), prefix='upload-')


class FormDataParser(werkzeug.formparser.FormDataParser):

    def __init__(self,
                 stream_factory=None,
                 charset='utf-8',
                 errors='replace',
                 max_form_memory_size=None,
                 max_content_length=None,
                 cls=None,
                 silent=True):
        super().__init__(mo_stream_factory, charset, errors, max_form_memory_size, max_content_length, cls, silent)


class Request(flask.wrappers.Request):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_data_parser_class = FormDataParser

    # Můžeme zvýšit maximální velikost souboru pro dávkové uploady
    custom_max_content_length: Optional[int] = None

    # Původně atribut ve werkzeug.BaseRequest, předefinován na property ve flask.Request
    @property
    def max_content_length(self):
        return self.custom_max_content_length or mo.config.MAX_CONTENT_LENGTH


# Flask interpretuje relativní cesty všelijak, tak mu vyrobíme absolutní
mo.config.DATA_DIR = os.path.abspath(mo.config.DATA_DIR)
static_dir = os.path.abspath('static')

# Aplikační objekt
app = Flask(__name__, static_folder=static_dir)
app.config.from_object(config)
app.request_class = Request
db.flask_db = SQLAlchemy(app, metadata=db.metadata)

Bootstrap(app)  # make bootstrap libs accessible for the app

# Budeme používat české locale
locale.setlocale(locale.LC_COLLATE, 'cs_CZ.UTF-8')


# Incializace logování

def setup_logging():
    # Logování v Pythonu je vymyšlené hezky, ale bohužel spousta modulů má tendenci
    # konfigurovat si u svých loggerů náhodné handlery. Pokud chceme, aby bylo logování
    # aspoň trochu jednotné, musíme jim to trochu předrátovat.

    # Nastavíme root logger, aby logoval po našem do flaskového logu
    log_handler = flask.logging.default_handler
    log_formatter = logging.Formatter(fmt='%(asctime)-15s.%(msecs)03d %(levelname)-5.5s [%(process)s] (%(name)s) %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')
    log_handler.setFormatter(log_formatter)
    root_logger = logging.getLogger()
    root_logger.addHandler(log_handler)

    # app.logger se jmenuje "mo.web" a propaguje se do root loggeru,
    # takže nechceme, aby měl svůj handler.
    app.logger.removeHandler(log_handler)
    app.logger.setLevel(logging.DEBUG)

    # sqlalchemy má logger, ke kterému si při prvním vypsání hlášky přidá handler,
    # není-li tam zatím žádný. Tak přidáme dummy handler. Vše se propaguje do root loggeru.
    sqla_logger = logging.getLogger('sqlalchemy.engine.base.Engine')
    sqla_logger.addHandler(logging.NullHandler())

    # werkzeug má vlastní handler s vlastním formátem hlášek, který už obsahuje timestamp,
    # takže vypneme propagování
    wz_logger = logging.getLogger('werkzeug')
    wz_logger.propagate = False


setup_logging()


# Incializace assetového mechanismu
mo.ext.assets.Assets(app, url_prefix='/assets', asset_dir=static_dir)

app.assets.add_assets([
    'bootstrap.min.css',
    'mo.css',
    'js/news-reloader.js',
])


# Inicializace požadavků a nucená autorizace

class NeedLoginError(werkzeug.exceptions.Forbidden):
    description = 'Need to log in'


def need_login():
    if not g.user:
        raise NeedLoginError()


def init_request():
    path = request.path
    if path.startswith('/static/') or path.startswith('/assets/'):
        # Pro statické soubory v development nasazení nepotřebujeme nastavovat
        # nic dalšího (v ostrém nasazení je servíruje uwsgi)
        return

    if 'uid' in session:
        user = mo.users.user_by_uid(session['uid'])
        if not user:
            # Uživatel mezitím přestal existovat
            app.logger.error('Zrušena session pro neexistujícího uživatele uid=%s', session['uid'])
            return mo.web.auth.logout()
    else:
        user = None

    g.user = user
    if have_uwsgi:
        uwsgi.set_logvar('osmo_uid', str(user.user_id) if user else '-')

    mo.now = mo.util.get_now()
    g.now = mo.now  # for templates
    mo.util.current_log_user = user

    # K některým podstromům je nutné mít speciální oprávnění
    # XXX: Když celá aplikace běží v adresáři, request.path je relativní ke kořeni aplikace, ne celého webu
    if path.startswith('/org/'):
        if not user:
            raise NeedLoginError()
        if not (user.is_org or user.is_admin):
            raise werkzeug.exceptions.Forbidden()
        g.gatekeeper = mo.rights.Gatekeeper(user)
    elif path.startswith('/user/'):
        if not user:
            raise NeedLoginError()


app.before_request(init_request)


### UWSGI glue ###

try:
    import uwsgi
    from uwsgidecorators import timer, signal

    # Čas od času se probudíme a projdeme joby pro případ, že by se ztratil signál.
    # Také při tom expirujeme zastaralé joby.
    @timer(config.JOB_GC_PERIOD, target='mule')
    def mule_timer(signum):
        # app.logger.debug('Mule: Timer tick')
        with app.app_context():
            mo.now = mo.util.get_now()
            mo.jobs.process_jobs()

    # Obykle při vložení jobu dostaneme signál.
    @signal(42, target='mule')
    def mule_signal(signum):
        app.logger.debug('Mule: Přijat signál')
        with app.app_context():
            mo.now = mo.util.get_now()
            mo.jobs.process_jobs()

    def wake_up_mule():
        app.logger.debug('Mule: Posíláme signál')
        uwsgi.signal(42)

    have_uwsgi = True
    mo.jobs.send_notify = wake_up_mule

except ImportError:
    app.logger.warn('Nenalezeno UWSGI, takže automatické notifikace nepoběží.')
    have_uwsgi = False


# Většina webu je v samostatných modulech
import mo.web.auth
import mo.web.jinja
import mo.web.menu
import mo.web.misc
import mo.web.org
import mo.web.org_contest
import mo.web.org_jobs
import mo.web.org_log
import mo.web.org_place
import mo.web.org_round
import mo.web.org_score
import mo.web.org_users
import mo.web.user