import functools
import logging
import os

from flask import Flask, g, render_template, request, redirect, session, url_for
from flask_bootstrap import Bootstrap

import db
import forms
import models
import util
from config import Config


logging.basicConfig(filename=Config.LOG_PATH, level=logging.DEBUG)

logging.info(f"Starting in working dir: {os.getcwd()}")


def create_app():
    app = Flask(__name__)
    Bootstrap(app)
    return app


app = create_app()

app.config.from_object(Config)


@app.template_filter("format_time")
def format_time(value):
    """Make the datetime to time string formatting available to jinja2"""
    if value is None:
        return ""
    return util.datetime_time_str(value)


@app.template_filter("format_timedelta")
def format_timedelta(value):
    """Make formatting for timedeltas available to jinja2"""
    if value is None:
        return ""
    return util.timedelta_to_str(value)


@app.cli.command("initdb")
def init_db_command():
    """Initializes the database."""
    db.init_db()


@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, "_database", None)
    if db is not None:
        db.close()


def set_user_role(data):
    """Set the users role in the flask g object for later usage"""
    g.is_editor = data == "editor"


def authorize(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            set_user_role(session["user_type"])
        except KeyError:
            return redirect("/login")
        return func(*args, **kwargs)

    return wrapper


def get_user_type(password):
    # TODO password hashing?
    if password == Config.WRITE_PW:
        return "editor"
    if password == Config.READ_PW:
        return "readonly"
    return False


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    else:
        user_type = get_user_type(request.form.get("password"))
        if not user_type:
            return redirect("/login")
        session["user_type"] = user_type
        return redirect("/")


@app.route("/logout")
def logout():
    session.pop("role", None)
    return redirect("login")


@app.route("/")
@authorize
def landing():
    return redirect("/season")


@app.route("/season")
@authorize
def season_list():
    sql, args = db.load_season()
    results = db.query_db(sql, args, cls=models.Season)
    model = {
        "seasons": results,
        "columns": [
            ("code", "#"),
            ("game", "Game"),
            ("description", "Season Description"),
            ("start", "Started At"),
            ("end", "Ended At"),
        ],
    }
    return render_template("season_list.html", model=model)


@app.route("/season/new", methods=["GET"])
@authorize
def season_new():
    form = forms.SeasonForm()
    model = models.GenericFormModel(
        page_title="New Season",
        form_title="Create New Season",
        post_url="/season/edit/null",
    )
    return render_template("generic_form.html", model=model, form=form)


@app.route("/season/edit/<season_id>", methods=["GET", "POST"])
@authorize
def season_edit(season_id: int):
    model = models.GenericFormModel(
        page_title="Seasons",
        form_title="Edit Season",
        post_url=f"/season/edit/{season_id}",
    )

    if request.method == "GET":
        sql, args = db.load_season(season_id)
        season: models.Season = db.query_db(sql, args, one=True, cls=models.Season)

        form = forms.SeasonForm()
        form.season_id.data = season.id
        form.code.data = season.code
        form.game_name.data = season.game
        form.description.data = season.description
        form.start.data = season.start
        form.end.data = season.end

        model.form_title = f"Edit Season '{season.code}: {season.game}'"
        return render_template("generic_form.html", model=model, form=form)
    else:
        form = forms.SeasonForm()

        if not form.validate_on_submit():
            model.errors = form.errors
            return render_template("generic_form.html", model=model, form=form)

        season = models.Season.from_form(form)
        sql, args = db.save_season_query(season)
        errors = db.update_db(sql, args)
        return redirect(url_for("season_list"))


@app.route("/season/<season_id>", methods=["GET"])
@authorize
def season_overview(season_id: int):
    sql, args = db.load_season(season_id)
    season = db.query_db(sql, args, one=True, cls=models.Season)

    sql, args = db.load_episodes(season.id)
    episodes = db.query_db(sql, args, cls=models.Episode)

    infos = {
        "Number": season.code,
        "Game": season.game,
        "Start Date": season.start,
        "End Date": season.end if season.end else "Ongoing",
    }
    model = {
        "title": f"{season.code} {season.game}",
        "season_info": infos,
        "episodes": episodes,
    }
    return render_template("season_overview.html", model=model)


@app.route("/season/<season_id>/episode/<episode_id>")
@authorize
def episode_detail(season_id: int, episode_id: int):
    sql, args = db.load_season(season_id)
    season = db.query_db(sql, args, one=True, cls=models.Season)
    sql, args = db.load_episode(episode_id)
    episode = db.query_db(sql, args, one=True, cls=models.Episode)
    sql, args = db.load_episode_players(episode_id)
    ep_players = db.query_db(sql, args, cls=models.Player)

    events = {"entries": [], "victory_count": 0, "defeat_count": 0}

    model = {
        "title": f"{season.code}{episode.code}",
        "episode": episode,
        "season": season,
        "players": ep_players,
        "events": events,
    }

    return render_template("episode_details.html", model=model)


@app.route("/season/<season_id>/episode", methods=["GET"])
@authorize
def episode_list(season_id: int):
    sql, args = db.load_season(season_id)
    season = db.query_db(sql, args, one=True, cls=models.Season)
    sql, args = db.load_episodes(season_id)
    episodes = db.query_db(sql, args, cls=models.Episode)

    model = {"season_id": season_id, "season_code": season.code}
    return render_template("episode_list.html", model=model)


@app.route("/season/<season_id>/episode/new", methods=["GET"])
@authorize
def episode_new(season_id: int):
    model = models.GenericFormModel(
        page_title="New Episode",
        form_title="Create New Episode",
        post_url=f"/season/{season_id}/episode/null/edit",
    )

    form = forms.EpisodeForm(request.form)
    form.season_id.data = season_id
    return render_template("generic_form.html", model=model, form=form)


@app.route("/season/<season_id>/episode/<episode_id>/edit", methods=["GET", "POST"])
@authorize
def episode_edit(season_id: int, episode_id: int):
    model = models.GenericFormModel(
        page_title="Edit Episode",
        form_title="Edit Episode",
        post_url=f"/season/{season_id}/episode/{episode_id}/edit",
    )

    if request.method == "GET":
        sql, args = db.load_episode(episode_id)
        episode: models.Episode = db.query_db(sql, args, one=True, cls=models.Episode)

        sql, args = db.load_episode_players(episode_id)
        ep_players = db.query_db(sql, args, cls=models.Player)

        form = forms.EpisodeForm()
        form.season_id.data = episode.season_id
        form.episode_id.data = episode.id
        form.code.data = episode.code
        form.date.data = episode.date
        form.start.data = episode.start
        form.end.data = episode.end
        form.title.data = episode.title
        form.players.data = [p.id for p in ep_players]

        model.form_title = f"Edit Episode '{episode.code}: {episode.title}'"
        return render_template("generic_form.html", model=model, form=form)
    else:
        form = forms.EpisodeForm()

        if not form.validate_on_submit():
            model.errors = form.errors
            return render_template("generic_form.html", model=model, form=form)

        errors = False
        episode = models.Episode.from_form(form)
        sql, args = db.save_episode(episode)

        last_key = db.update_db(sql, args, return_key=True)

        episode_id = episode.id if episode.id else last_key

        form_ids = form.players.data

        sql, args = db.load_episode_players(episode_id)
        ep_players = db.query_db(sql, args, cls=models.Player)
        pids = [p.id for p in ep_players]

        new_ids = [pid for pid in form_ids if pid not in pids]
        removed_ids = [pid for pid in pids if pid not in form_ids]

        if removed_ids:
            sql, args = db.remove_episode_player(episode_id, removed_ids)
            errors = db.update_db(sql, args)

        if new_ids:
            sql, args = db.save_episode_players(episode_id, new_ids)
            errors = db.update_db(sql, args)

        if errors:
            model.errors = {"Error saving episode": [errors]}
            return render_template("generic_form.html", model=model, form=form)
        return redirect(url_for("season_overview", season_id=season_id))


@app.route("/season/<s_id>/episode/<ep_id>/event/new", methods=["GET"])
@authorize
def event_new(s_id: int, ep_id: int):
    model = {
        "page_title": "New Event",
        "form_title": "Create New Event",
        "post_url": f"/seasons/{s_id}/episodes/{ep_id}/events/null/edit",
    }

    return render_template("event_editor.html", model=model)


@app.route(
    "/season/<s_id>/episode/<ep_id>/event/<ev_id>/edit", methods=["GET", "POST"]
)
@authorize
def event_edit(s_id: int, ep_id: int, ev_id: int):
    model = {
        "page_title": "Edit Event",
        "form_title": "Edit Event",
        "post_url": f"/season/{s_id}/episode/{ep_id}/event/{ev_id}/edit",
    }
    if request.method == "GET":
        return render_template("event_editor.html", model=model)
    else:
        pass


@app.route("/player/new", methods=["GET"])
@authorize
def player_new():
    form = forms.PlayerForm()
    model = models.GenericFormModel(
        page_title="Players",
        form_title="Create a new Player",
        post_url="/player/null/edit",
    )
    return render_template("generic_form.html", model=model, form=form)


@app.route("/player/<player_id>/edit", methods=["GET", "POST"])
@authorize
def player_edit(player_id: int):
    model = models.GenericFormModel(
        page_title="Players",
        form_title=f"Edit Player",
        post_url=f"/player/{player_id}/edit",
    )
    # Edit Existing Player
    if request.method == "GET":
        sql, args = db.load_players(player_id)
        player = db.query_db(sql, args, one=True, cls=models.Player)

        form = forms.PlayerForm()
        form.player_id.data = player.id
        form.anonymize.data = player.anon
        form.real_name.data = player.real_name
        form.alias.data = player.alias
        form.hex_id.data = player.hex_id

        model.form_title = f'Edit Player "{player.name}"'
        return render_template("generic_form.html", model=model, form=form)

    # Save POSTed data
    else:
        form = forms.PlayerForm()
        if form.validate_on_submit():
            player = models.Player.from_form(form)
            res = db.save_player(player)
            return redirect("/player")

        model.form_title = "Incorrect Data"
        return render_template("generic_form.html", model=model, form=form)


@app.route("/player")
@authorize
def player_list():
    sql, args = db.load_players()
    players = db.query_db(sql, args, cls=models.Player)
    model = {
        "player_list": players,
        "columns": [
            ("id", "ID"),
            ("name", "Player Name"),
            ("alias", "Alias"),
            ("hex_id", "Hex ID"),
        ],
    }
    return render_template("player_list.html", model=model)


@app.route("/drink")
@authorize
def drink_list():
    sql, args = db.load_drinks()
    drinks = db.query_db(sql, args, cls=models.Drink)
    model = {
        "drinks": drinks,
        "columns": [("id", "ID"), ("name", "Drink Name"), ("vol", "Alcohol %")],
        "controls": [("edit", "Edit")],
    }
    return render_template("drink_list.html", model=model)


@app.route("/drink/<drink_id>/edit", methods=["GET"])
@authorize
def drink_edit(drink_id: int):
    sql, args = db.load_drinks(drink_id)
    drink = db.query_db(sql, args, one=True, cls=models.Drink)

    form = forms.DrinkForm()
    form.drink_id.data = drink.id
    form.name.data = drink.name
    form.vol.data = drink.vol

    model = models.GenericFormModel(
        page_title="Edit Drink",
        form_title=f'Edit Drink "{drink.name}"',
        post_url="/drink/save",
    )

    return render_template("generic_form.html", model=model, form=form)


@app.route("/drink/new", methods=["GET"])
@authorize
def new_drink():
    form = forms.DrinkForm()

    model = models.GenericFormModel(
        page_title="New Drink",
        form_title=f"Create a new Drink",
        post_url="/drinks/save",
    )
    return render_template("generic_form.html", model=model, form=form)


@app.route("/drink/save", methods=["POST"])
@authorize
def drink_save():
    form = forms.DrinkForm()
    if form.validate_on_submit():
        drink = models.Drink.from_form(form)
        res = db.save_drink(drink)
        return redirect("/drink")

    model = models.GenericFormModel(
        page_title="Drinks", form_title="Edit Drink", post_url="/drink/save"
    )
    return render_template("generic_form.html", model=model, form=form)


@app.route("/enemy")
@authorize
def enemy_list():
    sql, args = db.load_enemies()
    enemies = db.query_db(sql, args, cls=models.Enemy)
    model = {"enemies": enemies}
    return render_template("enemies.html", model=model)


@app.route("/enemy/new", methods=["GET"])
@authorize
def enemy_new(preselect_season=None):
    form = forms.EnemyForm()

    if preselect_season:
        form.season_id.default = preselect_season

    model = models.GenericFormModel(
        page_title="Enemies",
        form_title="Create a new Enemy",
        post_url=f"/enemy/null/edit",
    )
    return render_template("generic_form.html", model=model, form=form)


@app.route("/enemy/<enemy_id>/edit", methods=["GET", "POST"])
@authorize
def enemy_edit(enemy_id: int):
    model = models.GenericFormModel(
        page_title="Enemies",
        form_title="Edit Enemy",
        post_url=f"/enemy/{enemy_id}/edit",
    )

    if request.method == "GET":
        sql, args = db.load_enemies(enemy_id)
        enemy = db.query_db(sql, args, one=True, cls=models.Enemy)

        form = forms.EnemyForm()
        form.season_id.data = enemy.season_id if enemy.season_id else -1
        form.name.data = enemy.name
        form.is_boss.data = enemy.boss
        form.enemy_id.data = enemy_id

        model.form_title = f'Edit Enemy "{enemy.name}"'
        return render_template("generic_form.html", model=model, form=form)
    else:
        form = forms.EnemyForm()
        if form.validate_on_submit():
            enemy = models.Enemy.from_form(form)
            sql, args = db.save_enemy(enemy)
            errors = db.update_db(sql, args)

            if form.submit_continue_button.data:
                form.name.data = None
                return enemy_new(preselect_season=enemy.season_id)
            return redirect("/enemy")

        model.form_title = "Incorrect Data"
        return render_template("generic_form.html", model=model, form=form)


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