import datetime
import requests
from functools import wraps
import re
from flask import Flask
from functools import wraps

import os
import grpc
#import servicesAdmin_pb2
#import servicesAdmin_pb2_grpc
#import servicesCloudConfig_pb2_grpc
#import servicesCloudConfig_pb2
from werkzeug.security import check_password_hash
#from flask_sqlalchemy import SQLAlchemy

from flask import jsonify, request, make_response
import jwt

from flask_cors import CORS
import sys

app = Flask(__name__)
cors = CORS(app, resources={r"/*": {"origins": "*"}})

# app.config[
#     "SQLALCHEMY_DATABASE_URI"
# ] = "mysql://root:8uTinBvrJMBx4v&2@127.0.0.1:3306/login"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = "055d48d7699d412c5015ece728cfd847"

client_host = os.getenv("CLIENT_HOST", "localhost")

PATH_APP = os.path.dirname( __file__ )

path_app_main = os.path.join( PATH_APP, 'App/main' )
sys.path.append(path_app_main)
path_app_protos = os.path.join( PATH_APP, 'App/protos' )
sys.path.append(path_app_protos)


#db = SQLAlchemy(app)


# class Users(db.Model):
#     id = db.Column(db.Integer, primary_key=True)
#     public_id = db.Column(db.String(128))
#     username = db.Column(db.String(50))
#     password = db.Column(db.String(128))
#     email = db.Column(db.String(64))
#     tenant_id = db.Column(db.Integer)


# class Tenant(db.Model):
#     id = db.Column(db.Integer, primary_key=True)
#     name = db.Column(db.String(64))
#     vcd_host = db.Column(db.String(128))
#     vcd_api_version = db.Column(db.String(30))


# with app.app_context():
#     db.create_all()


def token_required(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        token = None
        from_vcd = False

        if "x-daas-token" in request.headers:
            token = request.headers["x-daas-token"]
        elif request.headers["authorization"] and request.args["tenant_id"]:
            token = request.headers["authorization"]  # Bearer token
            from_vcd = True
        else:
            return jsonify({"message": "No se encuentra autenticado"})

        if not from_vcd:
            try:
                data = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"])
                current_user = Users.query.filter_by(
                    public_id=data["public_id"]
                ).first()  # Requiere query con filtrado
            except:
                return jsonify({"message": "token is invalid"})

            # if data['exp'] >= datetime.datetime.now():
            #     return jsonify({"message": "expired token"})
        else:
            tenant = Tenant.query.filter_by(id=request.args["tenant_id"]).first()
            api_version = tenant.vcd_api_version
            api_versions = []

            if api_version is None or api_version.strip() == '':
                try:
                    res = requests.get(
                        f"{tenant.vcd_host}/cloudapi/1.0.0/orgs",   # vcd_host se puede recuperar de plugin (?)
                        timeout=120,
                        headers={
                            "Authorization": token,
                        },
                    )   # Al no contar con el header accept en el formato requerido según la documentación de la API, nos arroja el listado de versiones de API

                    if res.status_code != 406:
                        res.raise_for_status()
                    if "minorErrorCode" in res.json() and res.json()["minorErrorCode"] == "NOT_ACCEPTABLE":
                        r = re.search(r"(?<=\[)(.*)(?=\])", res.json()["message"])
                        api_versions = r.group(1).split(",")
                        api_versions = [x.strip() for x in api_versions if "D" not in x]    # D es para versiones obsoletas (Deprecated)
                    else:
                        # Es posible que ya no se entreguen las versiones de API desde una petición incompleta (sin header Accept requerida)
                        return jsonify({"message": "La validación de token falló, reporte éste error con su proveedor del servicio"})
                except:
                    return jsonify({"message": "No fue posible validar versión de api VCD"})
            else:
                api_versions.append(api_version)
 
            for av in api_versions:
                try:
                    res = requests.get(
                        f"{tenant.vcd_host}/cloudapi/1.0.0/orgs",
                        timeout=120,
                        headers={
                            "Accept": f"application/*;version={av}",
                            "Authorization": token,
                        },
                    )

                    if res.status_code == 406:
                        continue
                    
                    res.raise_for_status()

                    if not "resultTotal" in res.json() or res.json()["resultTotal"] < 1:
                        # La respuesta a la petición cambio, hay que cambiar a otra petición simple de la api vcd
                        return jsonify({"message": "No fue posible validar vigencia de token, reporte éste error con su proveedor del servicio"})
                    else:
                        # Actualizar versión de API en tabla de organización (?)
                        current_user = tenant
                        break
                except:
                    return jsonify({"message": "No se autenticó"})
        return f(token, *args, **kwargs)

    return decorator


@app.route("/session", methods=["POST"])
def create_session():
    if request.method == "POST":
        auth = request.authorization

        if not auth or not auth.username or not auth.password:
            return make_response(
                "could not verify",
                401,
                {"Authentication": 'Se requiere inicio de sesión"'},
            )

        full_username = re.match("(.*)@(.*)", auth.username)

        if not full_username:
            return jsonify({"message": "Se requiere especificar usuario + tenant"})

        tenant = Tenant.query.filter_by(name=full_username[2]).first()

        if not tenant:
            return jsonify({"message": f"No se encontró tenant '{full_username[2]}'"})

        user_found = Users.query.filter_by(
            username=full_username[1], tenant_id=tenant.id
        ).first()  # Requiere query con filtrado

        if not user_found:
            return jsonify({"message": "Usuario no existente"})

        if check_password_hash(user_found.password, auth.password):
            token = jwt.encode(
                {
                    "public_id": user_found.public_id,
                    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
                },
                app.config["SECRET_KEY"],
                "HS256",
            )

            return jsonify({"token": token})

        return make_response(
            "could not verify",
            401,
            {"Authentication": '"Se requiere inicio de sesión"'},
        )


@app.route("/category_services")
#@token_required
#def get_categories(self):
def get_categories():
    if request.method == "GET":
        with open("server.pem", "rb") as fp:
            ca_cert = fp.read()
        
        credential = grpc.ssl_channel_credentials(
            root_certificates=open("ca.pem", "rb").read(),#client_cert.ca_cert,
            private_key=open("server.key", "rb").read(), #client_cert.key,
            certificate_chain=open("server.pem", "rb").read() #client_cert.cert
        )

        creds = grpc.ssl_channel_credentials(ca_cert)
        core_channel = grpc.secure_channel("localhost:8443", credentials=creds)

        # cert = crypto.load_certificate(crypto.FILETYPE_PEM, open("server.pem", "rb").read())
        # subject = cert.get_subject()
        # issued_to = subject.CN    # the Common Name field
        # issuer = cert.get_issuer()
        # issued_by = issuer.CN

        # print(issued_to)
        # print(issued_by)

        stub = servicesCloudConfig_pb2_grpc.servicesCloudConfigServiceStub(core_channel)
        req = servicesCloudConfig_pb2.ProductTypeRequest()
        res = stub.getProductType(req)

        #print(res.response.response)

        return jsonify(res.response.response)


@app.route("/services")
@token_required
def get_services():
    if request.method == "GET":
        with open("ca.pem", "rb") as fp:
            ca_cert = fp.read()

        creds = grpc.ssl_channel_credentials(ca_cert)
        core_channel = grpc.secure_channel(f"{client_host}:443", credentials=creds)
        stub = servicesCloudConfig_pb2_grpc.servicesCloudConfigServiceStub(core_channel)
        req = servicesCloudConfig_pb2.ServicesByProductTypeRequest(
            IdProductType=request.args["service_type_id"]
        )
        res = stub.getServicesCatalog(req)

        return res


@app.route("/plans")
@token_required
def get_plans():
    if request.method == "GET":
        with open("ca.pem", "rb") as fp:
            ca_cert = fp.read()

        creds = grpc.ssl_channel_credentials(ca_cert)
        core_channel = grpc.secure_channel(f"{client_host}:443", credentials=creds)
        stub = servicesCloudConfig_pb2_grpc.servicesCloudConfigServiceStub(core_channel)
        req = servicesCloudConfig_pb2.PlansServiceRequest(
            idService=request.args["service_id"]
        )
        res = stub.getPlansService(req)

        return res


@app.route("/provisioning", methods=["POST"])
@token_required
def provide():
    if request.method == "POST":
        with open("ca.pem", "rb") as fp:
            ca_cert = fp.read()

        creds = grpc.ssl_channel_credentials(ca_cert)
        core_channel = grpc.secure_channel(f"{client_host}:443", credentials=creds)
        stub = servicesAdmin_pb2_grpc.ServiceservicesAdminStub(core_channel)
        req = servicesAdmin_pb2.ContractedServicesRequest(
            backup_scheduled_id=request.args["backup_scheduled_id"],
            firewall_id=request.args["firewall_id"],
            plan_id=request.args["plan_id"],
            user_id=request.args["user_id"],
        )
        res = stub.getServicesAllContracted(req)

        return res


@app.route("/services_contractead")
@token_required
def get_contracted():
    if request.method == "POST":
        with open("ca.pem", "rb") as fp:
            ca_cert = fp.read()

        creds = grpc.ssl_channel_credentials(ca_cert)
        core_channel = grpc.secure_channel(f"{client_host}:443", credentials=creds)
        stub = servicesAdmin_pb2_grpc.servicesCloudConfigServiceStub(core_channel)
        req = servicesAdmin_pb2.ServicesAllContractedRequest(
            idUser=request.args["user_id"]
        )
        res = stub.getServicesAllContracted(req)

        return res


@app.route("/services_security",methods=["POST", "GET"])
#@token_required
def services_security():
    if request.method == "GET":
        events_ids = request.form['events_ids'] if len(request.form.getlist('events_ids')) > 0 else 'NOTFOUND'
        if events_ids == 'srv-apigateway':
            from AxwaySrv import AxwaySrv as axway
            return axway.main(request, PATH_APP)


@app.route("/wsoa",methods=["POST", "GET"])
#@token_required
def wsoa():
    if request.method == "POST":
        events_ids = request.form['events_ids'] if len(request.form.getlist('events_ids')) > 0 else 'NOTFOUND'
        if events_ids == 'srv-woa':
            from WSOA import WSOA as wsoa
            return wsoa.main(request, PATH_APP)


@app.route("/kmshsm",methods=["POST", "GET"])
#@token_required
def fortanix():
    if request.method == "POST":
        events_ids = request.form['events_ids'] if len(request.form.getlist('events_ids')) > 0 else 'NOTFOUND'
        if events_ids == 'srv-fortanix':
            from Fortanix import Fortanix as fortanix
            return fortanix.main(request, PATH_APP)


@app.route("/hello",methods=["POST", "GET"])
#@token_required
def hello():
    return "HELLO FROM FLASK"

if __name__ == "__main__":
    app.run()
    #app.run(port = 5000, debug = True)
