from flask import Flask, jsonify
from flask_cors import CORS
from flask_jwt_extended import JWTManager
import flask_migrate
from jsonschema.exceptions import ValidationError
from NamedAtomicLock import NamedAtomicLock
from werkzeug.exceptions import BadRequest

# These imports are required
from areas import api_v1
from cliapp import cli
from web import web

from areas import users
from areas import apps
from areas import auth
from areas import roles
from cliapp import cliapp
from web import login

from database import db

from helpers import (
    BadRequest,
    KratosError,
    HydraError,
    Unauthorized,
    bad_request_error,
    validation_error,
    kratos_error,
    global_error,
    hydra_error,
    unauthorized_error,
)

import cluster_config
from config import *
import logging
import migration_reset
import sys

# Configure logging.
from logging.config import dictConfig
dictConfig({
    'version': 1,
    'formatters': {'default': {
        'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
    }},
    'handlers': {'wsgi': {
        'class': 'logging.StreamHandler',
        'stream': 'ext://flask.logging.wsgi_errors_stream',
        'formatter': 'default',
    }},
    'root': {
        'level': 'INFO',
        'handlers': ['wsgi'],
    }
})

app = Flask(__name__)

app.config["SECRET_KEY"] = SECRET_KEY
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = SQLALCHEMY_TRACK_MODIFICATIONS

app.logger.setLevel(logging.INFO)
app.logger.info("Starting dashboard backend.")

cors = CORS(app)

db.init_app(app)

# We'll now perform some initialization routines. Because these have to be done
# once at startup, not for every gunicorn worker, we take a machine-wide lock
# for this.
init_lock = NamedAtomicLock('dashboard_init')
if init_lock.acquire():
    try:
        with app.app_context():
            # We have reset the alembic migration history at Stackspin version 2.2.
            # This checks whether we need to prepare the database to follow that
            # change.
            migration_reset.reset()
        flask_migrate.Migrate(app, db)
        try:
            with app.app_context():
                flask_migrate.upgrade()
        except Exception as e:
            app.logger.info(f"upgrade failed: {type(e)}: {e}")
            sys.exit(2)

        # We need this app context in order to talk the database, which is managed by
        # flask-sqlalchemy, which assumes a flask app context.
        with app.app_context():
            # Load the list of apps from a configmap and store any missing ones in the
            # database.
            cluster_config.populate_apps()
            # Same for the list of oauthclients.
            cluster_config.populate_oauthclients()
    finally:
        init_lock.release()

app.register_blueprint(api_v1)
app.register_blueprint(web)
app.register_blueprint(cli)

# Error handlers
app.register_error_handler(Exception, global_error)
app.register_error_handler(BadRequest, bad_request_error)
app.register_error_handler(ValidationError, validation_error)
app.register_error_handler(KratosError, kratos_error)
app.register_error_handler(HydraError, hydra_error)
app.register_error_handler(Unauthorized, unauthorized_error)

jwt = JWTManager(app)

# When token is not valid or missing handler
@jwt.invalid_token_loader
@jwt.unauthorized_loader
@jwt.expired_token_loader
def expired_token_callback(*args):
    return jsonify({"errorMessage": "Unauthorized"}), 401


@app.route("/")
def index():
    return "Stackspin API v1.0"