from flask import Flask, redirect, flash, render_template, session, g, request, get_flashed_messages
import werkzeug.exceptions
import json
import traceback
from datetime import datetime

import hra.config as config
import hra.db as db
from hra.web import app, NeedLoginError
import hra.web.jinja_mac as jinja_mac
import hra.lib as lib

def log(team, endpoint, status, **kvarg):
    x = db.Log(
            source_ip=request.remote_addr,
            user_id=g.user.id,
            team_id=team.team_id,
            game_id=team.game_id,
            endpoint=endpoint,
            status=status,
            url=request.path,
            get=request.args,
            time=datetime.now(),
            **kvarg
    )
    db.get_session().add(x)
    return x


def json_endpoint(f):
    def l(*arg, **kvarg):
        x = f(*arg, **kvarg)
        response = app.response_class(
            response=json.dumps(x),
            status=200,
            mimetype='application/json'
        )
        return response
    l.__name__ = f.__name__
    return l


def args_get(name, type, optional=False, default=None):
    v = request.args.get(name)
    if v is None:
        if optional:
            return default
        else:
            raise werkzeug.exceptions.BadRequest(f"Missing mandatory option {name}.")
    try:
        return type(v)
    except ValueError:
        raise werkzeug.exceptions.BadRequest(f"Option {name} have wrong type.")


def get_context():
    if g.user is None:
        raise NeedLoginError
    game_name = args_get("game", str, True, "main")
    team = db.get_session().query(db.Team).filter_by(user_id=g.user.id, name=game_name).one_or_none()
    if team is None:
        raise werkzeug.exceptions.NotFound("No such game")
    return team


@app.route("/api/state", methods=['GET'])
@json_endpoint
def api_state():
    ses = db.get_session()
    team = get_context()
    game = team.game
    game = game.lock()
    team_id = team.team_id
    min_round = args_get("min_round", int, True)
    t = datetime.now()
    state = game.current_state()
    if state is None:
        log(team, db.Endpoint.state, "working", text="wait 1")
        ses.commit()
        return {
            "status": "working",
            "wait": 1.0,
        }
    if game.step_mode == db.StepMode.automatic:
        time_to_end = max(1, game.step_every_s - (t - state.create_time).total_seconds())
    else:
        time_to_end = None
    if min_round is not None and min_round > game.current_round:
        wait = time_to_end + 1.0 if time_to_end is not None else 3.0
        log(team, db.Endpoint.state, "too_early", text=f"wait {wait}; min_round {min_round}", round=game.current_round)
        ses.commit()
        return {
            "status": "too_early",
            "wait": wait,
        }
    move = team.get_move(state.round)
    move.reads_count += 1
    log(team, db.Endpoint.state, "ok", round=game.current_round)
    ses.commit()
    return {
        "status": "ok",
        "round": state.round,
        "team_id": team_id,
        "teams_count": game.teams_count,
        "time_to_response": time_to_end,
        "state": game.get_logic().personalize_state(state.get_state(), team_id, state.round),
    }

@app.route("/api/action", methods=['POST'])
@json_endpoint
def api_action():
    ses = db.get_session()
    def to_late():
        log(team, db.Endpoint.action, "too_late", round=round_id)
        ses.commit()
        return {
            "status": "too_late",
        }
    team = get_context()
    game = team.game
    team_id = team.team_id
    j = request.get_json()
    state = game.current_state()
    round_id = args_get('round', int)
    if round_id < 0 or round_id > game.current_round:
        raise werkzeug.exceptions.BadRequest("Wrong round.")
    if game.working_on_next_state or round_id < game.current_round:
        return to_late()

    try:
        warnings = game.get_logic().validate_move(state.get_state(), team_id, j, round_id)
    except Exception as e:
        game = game.lock()
        move = team.get_move(state.round)
        move.err_pushs_count += 1
        description =f"{type(e).__name__}: {e}"
        log(team, db.Endpoint.action, "error", round=round_id, text=description, data=db.new_big_data(traceback.format_exc()))
        ses.commit()
        return {
            "status": "error",
            "description": description
        }

    ses.expire_all()
    game = game.lock()

    if game.working_on_next_state or round_id < game.current_round:
        ses.commit()
        return to_late()

    move = team.get_move(state.round)

    move.move = db.new_big_data(j)

    if warnings is None:
        move.ok_pushs_count += 1
    else:
        move.warnings_pushs_count += 1

    log(team, db.Endpoint.action, "warning" if warnings is not None else "ok", round=round_id, data=move.move)
    ses.commit()

    if warnings is not None:
        return warnings
    return {
        "status": "ok",
    }


@app.route("/api/step", methods=['POST'])
@json_endpoint
def api_step():
    team = get_context()
    game = team.game
    if game.step_mode != db.StepMode.user:
        raise werkzeug.exceptions.Forbidden("Je zakázáno krokovat tuto hru")

    try:
        lib.game_step(game.game_id, by_user=True)
    except lib.DuplicitMakeStep:
        log(team, db.Endpoint.step, "too_early", text="Duplicit")
        db.get_session().commit()
        return {
            "status": "too_early",
            "wait": 5,
        }
    except lib.TooEarlyStep:
        log(team, db.Endpoint.step, "too_early")
        db.get_session().commit()
        return {
            "status": "too_early",
            "wait": 5,
        }
    else:
        log(team, db.Endpoint.step, "ok")
        db.get_session().commit()
        return {
            "status": "ok",
        }