import json
import urllib.request
from urllib.error import HTTPError
import uuid
from datetime import datetime, timedelta
from os import environ

from cryptography.exceptions import InvalidSignature
from flask import Flask, request, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = environ['DATABASE_URL']
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

metadata = MetaData()
db = SQLAlchemy(app, metadata=metadata)

from auth import handle_auth_request
from security.auth_server import *
from Models import UserModel
from Unprotected.HandleRequest import handle_unprotected_request, UNPROTECTED_PATHS


@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE'])
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def catch_all(path):
    if path == "auth":
        try:
            load = request.get_json(silent=False)
            res = handle_auth_request(load)
            return res

        except TypeError:
            response = app.response_class(
                response='{\n    "response": "bad request"\n}',
                status=400,
                mimetype='application/json'
            )
            return response

        except InvalidSignature:
            response = app.response_class(
                response='{\n    "response": "invalid signature"\n}',
                status=403,
                mimetype='application/json'
            )
            return response

    elif path in UNPROTECTED_PATHS:
        return handle_unprotected_request(request, path)

    try:
        token = json.loads(request.headers["access_token"])
        token = verify(token["token"], token["signature"])
        user_id = token["user_id"]
        user = db.session.query(UserModel).filter(UserModel.id == uuid.UUID(user_id)).first()

        if not user:
            raise ExpiredTokenException
        if user.login_id != uuid.UUID(token["login_id"]):
            raise ExpiredTokenException
        if datetime.fromisoformat(token["date"]) + timedelta(hours=24) < datetime.utcnow():
            raise ExpiredTokenException
        request_url = request.url.removeprefix(request.host_url)

        if "?" in request_url:
            request_url += f"&user_id={user_id}"
        else:
            request_url += f"?user_id={user_id}"

        new_url = f"http://api_server.local/{request_url}"
        method = request.method

        cl = request.content_length
        if cl is not None and cl > 10 * 1024 * 1024:  # 10MB
            raise ContentTooLongException
        data = request.get_data()
        headers = {}
        for key in request.headers.keys():
            if key != "access_token":
                headers[key] = request.headers[key]
        user.is_active()
        req = urllib.request.Request(new_url, data=data, headers=headers, method=method)
        try:
            req = urllib.request.urlopen(req)
            response = Response(
                req.read(),
                headers=dict(req.info())
            )
            return response
        except HTTPError as error:
            response = Response(
                error.read(),
                headers=dict(error.headers),
                status=error.code
            )
            return response
        except Exception as e:
            response = app.response_class(
                response='{\n    "response": "' + str(e) + '"\n}',
                status=502,
                mimetype='application/json'
            )
            return response

    except KeyError:
        response = app.response_class(
            response='{\n    "response": "missig access token"\n}',
            status=401,
            mimetype='application/json'
        )
        return response

    except ExpiredTokenException:
        response = app.response_class(
            response='{\n    "response": "your token is expired"\n}',
            status=401,
            mimetype='application/json'
        )
        return response

    except ContentTooLongException:
        response = app.response_class(
            response='{\n    "response": "your request data exceeded our maximum content length"\n}',
            status=413,
            mimetype='application/json'
        )
        return response

    except MethodNotAllowed:
        response = app.response_class(
            response='{\n    "response": "you are using a test account. no put only get."\n}',
            status=401,
            mimetype='application/json'
        )
        return response


@app.after_request
def after_request_func(response):
    db.session.close()
    return response


class ContentTooLongException(Exception):
    pass


class ExpiredTokenException(Exception):
    pass


class MethodNotAllowed(Exception):
    pass
