import random
import time
import mimetypes

from flask import flash, redirect, url_for
from loguru import logger
import helpers.custom_logging
from flask import Flask, request, render_template, Response
from flask_migrate import Migrate
from werkzeug.middleware.proxy_fix import ProxyFix

import config
import helpers.time_helper
from helpers.file_utils import *

mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')


def create_app(skip_pwndoc: bool = False):
    from helpers.db_models import db

    from api_process_findings import bp as process_findings_bp
    from api_templates import bp as templates_bp
    from api_debug import bp as debug_bp
    from api_root import bp as root_bp
    from api_pwndoc_audit import bp as pwndoc_bp
    import rq_dashboard

    logger.add("user_data/importer-logs/main.log", rotation="1 week", retention="60 days", compression="gz", encoding="utf8")
    Path(relative_path('api_examples/')).mkdir(parents=True, exist_ok=True)

    app = Flask(__name__, static_url_path=f"{config.IMPORTER_URL_PREFIX}/static")

    if config.BEHIND_REVERSE_PROXY:
        # Note that some settings do need to be manually set in nginx.conf
        app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=0)

    app.config['SQLALCHEMY_DATABASE_URI'] = config.DB_URI
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db.init_app(app)

    migrate = Migrate()

    # https://github.com/miguelgrinberg/Flask-Migrate/issues/61#issuecomment-208131722
    with app.app_context():
        if db.engine.url.drivername == 'sqlite':
            migrate.init_app(app, db, render_as_batch=True, compare_type=True)
        else:
            migrate.init_app(app, db)

    # db.create_all() need to happen single threaded (or staggered)
    time.sleep(random.random())  # todo: do this properly
    with app.app_context():
        db.create_all()

    app.config['UPLOAD_FOLDER'] = config.FLASK_UPLOAD_FOLDER
    app.config['MAX_CONTENT_LENGTH'] = 100 * 1000 * 1000
    app.config["JSONIFY_PRETTYPRINT_REGULAR"] = True
    app.config["SECRET_KEY"] = config.SECRET_KEY

    Path(app.config['UPLOAD_FOLDER']).mkdir(parents=True, exist_ok=True)

    single_threaded_init(app, skip_pwndoc)

    app.register_blueprint(process_findings_bp, url_prefix=f'{config.IMPORTER_URL_PREFIX}/findings')
    app.register_blueprint(templates_bp, url_prefix=f'{config.IMPORTER_URL_PREFIX}/templates')
    app.register_blueprint(pwndoc_bp, url_prefix=f'{config.IMPORTER_URL_PREFIX}/pwndoc_audit')
    app.register_blueprint(root_bp, url_prefix=f'{config.IMPORTER_URL_PREFIX}/')

    if not config.RQ_DISABLED:
        app.config.from_object(rq_dashboard.default_settings)
        app.config["RQ_DASHBOARD_REDIS_URL"] = f'redis://{config.REDIS_HOST}:{config.REDIS_PORT}'
        app.register_blueprint(rq_dashboard.blueprint, url_prefix=f"{config.IMPORTER_URL_PREFIX}/rq")
    else:
        @app.route(f"{config.IMPORTER_URL_PREFIX}/rq")
        @app.route(f"{config.IMPORTER_URL_PREFIX}/rq/")
        def rq_disabled():
            flash("Redis scheduling is disabled.", "info")
            return render_template('base.html')

    # Todo: possibly disable this or require additional password
    app.register_blueprint(debug_bp, url_prefix=f'{config.IMPORTER_URL_PREFIX}/debug')

    @app.errorhandler(500)
    def internal_error(error):
        flash(f"Internal server error (more information is in server logs)", "danger")
        return render_template('base.html'), 500

    @app.errorhandler(404)
    def internal_error(error):
        flash(f"The requested URL doesn't exist.", "danger")
        return render_template('base.html'), 404

    if config.IMPORTER_URL_PREFIX != "/":
        @app.route("/")
        def raw_root():
            return redirect(url_for('api_root.homepage'))

    app.jinja_env.filters['timestamp_to_iso'] = helpers.time_helper.timestamp_to_iso

    return app


def single_threaded_init(app, skip_pwndoc: bool):
    from helpers.template_grouping import TemplateGrouping
    import pwndoc_db_init

    main_thread = True if skip_pwndoc else pwndoc_db_init.InitialData.setup_first_user()

    if not main_thread:
        return  # todo: All threads except the main one will be responsive before the full setup is completed. User actions could cause race condition.

    pwndoc_db_init.InitialData().upload_initial_data()
    logger.info("PwnDoc Init completed")

    pwndoc_db_init.PwnDocUpdate.setup_scan2report_plugin_folder()  # This will fail if run second time.
    TemplateGrouping.add_new_gids()

    if skip_pwndoc:
        return

    pwndoc_db_init.PwnDocUpdate.add_new_fields_if_needed()

    with app.app_context():
        pwndoc_db_init.PwnDocUpdate.upload_templates_from_scan2report_repository()


if __name__ == "__main__":
    app = create_app()
    app.run()
