import json
import logging
import base64

import requests
from authlib.integrations.flask_oauth2 import current_token
from authlib.oauth2 import OAuth2Error
from flask import (
    Blueprint,
    g,
    jsonify,
    redirect,
    render_template,
    request,
    session,
    url_for,
    current_app as app
)
from flask_login import login_required
from flask.views import View

from ereuse_devicehub import __version__, messages
from ereuse_devicehub.db import db
from ereuse_devicehub.modules.oidc.forms import (
    AuthorizeForm,
    CreateClientForm,
    ListInventoryForm,
)
from ereuse_devicehub.modules.oidc.models import (
    MemberFederated,
    OAuth2Client,
    Code2Roles
)
from ereuse_devicehub.modules.oidc.oauth2 import (
    authorization,
    generate_user_info,
    require_oauth,
)
from ereuse_devicehub.views import GenericMixin

oidc = Blueprint('oidc', __name__, url_prefix='/', template_folder='templates')
logger = logging.getLogger(__name__)


##########
# Server #
##########
class CreateClientView(GenericMixin):
    methods = ['GET', 'POST']
    decorators = [login_required]
    template_name = 'create_client.html'
    title = "Edit Open Id Connect Client"

    def dispatch_request(self):
        form = CreateClientForm()
        if form.validate_on_submit():
            form.save()
            next_url = url_for('core.user-profile')
            return redirect(next_url)

        self.get_context()
        self.context.update(
            {
                'form': form,
                'title': self.title,
            }
        )
        return render_template(self.template_name, **self.context)


class AuthorizeView(GenericMixin):
    methods = ['GET', 'POST']
    decorators = [login_required]
    template_name = 'authorize.html'
    title = "Authorize"

    def dispatch_request(self):
        form = AuthorizeForm()
        client = OAuth2Client.query.filter_by(
            client_id=request.args.get('client_id')
        ).first()
        if not client:
            messages.error('Not exist client')
            return redirect(url_for('core.user-profile'))

        if form.validate_on_submit():
            if not form.consent.data:
                return redirect(url_for('core.user-profile'))

            return authorization.create_authorization_response(grant_user=g.user)

        try:
            grant = authorization.validate_consent_request(end_user=g.user)
        except OAuth2Error as error:
            messages.error(error.error)
            return redirect(url_for('core.user-profile'))

        self.get_context()
        self.context.update(
            {'form': form, 'title': self.title, 'user': g.user, 'grant': grant}
        )
        return render_template(self.template_name, **self.context)


class IssueTokenView(GenericMixin):
    methods = ['POST']
    decorators = []

    def dispatch_request(self):
        return authorization.create_token_response()


class OauthProfileView(GenericMixin):
    methods = ['GET']
    decorators = []
    template_name = 'authorize.html'
    title = "Authorize"

    @require_oauth('profile')
    def dispatch_request(self):
        return jsonify(generate_user_info(current_token.user, current_token.scope))


##########
# Client #
##########
class SelectInventoryView(GenericMixin):
    methods = ['GET', 'POST']
    decorators = []
    template_name = 'select_inventory.html'
    title = "Select an Inventory"

    def dispatch_request(self):
        host = app.config.get('HOST', '').strip("/")
        url = "https://ebsi-pcp-wallet-ui.vercel.app/oid4vp?"
        url += f"client_id=https://{host}&"
        url += "presentation_definition_uri=https://iotaledger.github.io"
        # url += "/ebsi-stardust-components/public/presentation-definition-ex1.json"
        url += "/ebsi-stardust-components/public//presentation-definition-ereuse.json&"
        url += f"response_uri=https://{host}/allow_code_oidc4vp"
        url += "&state=1700822573400&response_type=vp_token&response_mode=direct_post"
        url += "&nonce=DybC3A=="

        next = request.args.get('next', '#')
        session['next_url'] = next

        return redirect(url, code=302)


class AllowCodeView(GenericMixin):
    methods = ['GET', 'POST']
    decorators = []
    userinfo = None
    token = None
    discovery = {}

    def dispatch_request(self):
        self.code = request.args.get('code')
        self.oidc = session.get('oidc')
        if not self.code or not self.oidc:
            return self.redirect()

        self.member = MemberFederated.query.filter(
            MemberFederated.dlt_id_provider == self.oidc,
            MemberFederated.client_id.isnot(None),
            MemberFederated.client_secret.isnot(None),
        ).first()

        if not self.member:
            return self.redirect()

        self.get_token()
        if 'error' in self.token:
            messages.error(self.token.get('error', ''))
            return self.redirect()

        self.get_user_info()
        return self.redirect()

    def get_discovery(self):
        if self.discovery:
            return self.discovery

        try:
            url_well_known = self.member.domain + '.well-known/openid-configuration'
            self.discovery = requests.get(url_well_known).json()
        except Exception:
            self.discovery = {'code': 404}

        return self.discovery

    def get_token(self):
        data = {'grant_type': 'authorization_code', 'code': self.code}
        url = self.member.domain + '/oauth/token'
        url = self.get_discovery().get('token_endpoint', url)

        auth = (self.member.client_id, self.member.client_secret)
        msg = requests.post(url, data=data, auth=auth)
        self.token = json.loads(msg.text)

    def redirect(self):
        url = session.get('next_url') or '/login'
        return redirect(url)

    def get_user_info(self):
        if self.userinfo:
            return self.userinfo
        if 'access_token' not in self.token:
            return

        url = self.member.domain + '/oauth/userinfo'
        url = self.get_discovery().get('userinfo_endpoint', url)
        access_token = self.token['access_token']
        token_type = self.token.get('token_type', 'Bearer')
        headers = {"Authorization": f"{token_type} {access_token}"}

        msg = requests.get(url, headers=headers)
        self.userinfo = json.loads(msg.text)
        rols = self.userinfo.get('rols', [])
        session['rols'] = [(k, k) for k in rols]
        return self.userinfo


class AllowCodeOidc4vpView(GenericMixin):
    methods = ['POST']
    decorators = []
    userinfo = None
    token = None
    discovery = {}

    def dispatch_request(self):
        vcredential = self.get_credential()
        if not vcredential:
            return jsonify({"error": "No there are credentials"})

        roles = self.verify(vcredential)
        if not roles:
            return jsonify({"error": "No there are roles"})

        uri = self.get_response_uri(roles)

        return jsonify({"redirect_uri": uri})

    def get_credential(self):
        self.vp_token = request.values.get("vp_token")
        pv = self.vp_token.split(".")
        token = json.loads(base64.b64decode(pv[1]).decode())
        return token.get('vp', {}).get("verifiableCredential")

    def verify(self, vcredential):
        WALLET_INX_EBSI_PLUGIN_TOKEN = app.config.get(
            'WALLET_INX_EBSI_PLUGIN_TOKEN'
        )
        WALLET_INX_EBSI_PLUGIN_URL = app.config.get(
            'WALLET_INX_EBSI_PLUGIN_URL'
        )
        headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {WALLET_INX_EBSI_PLUGIN_TOKEN}'
        }
        for v in vcredential:
            data = json.dumps({
                "type": "VerificationRequest",
                "jwtCredential": v
            })
            result = requests.post(
                WALLET_INX_EBSI_PLUGIN_URL,
                headers=headers,
                data=data
            )
            if result.status_code != 200:
                return

            vps = json.loads(result.text)
            try:
                roles = vps['credential']['credentialSubject'].get('role')
            except Exception:
                roles = None

            if roles:
                break

        if not vps.get('verified'):
            return

        return roles

    def get_response_uri(selfi, roles):
        code = Code2Roles(roles=roles)
        db.session.add(code)
        db.session.commit()

        url = "https://{host}/allow_code_oidc4vp2?code={code}".format(
            host=app.config.get('HOST'),
            code=code.code
        )
        return url


class AllowCodeOidc4vp2View(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        self.code = request.args.get('code')
        if not self.code:
            return self.redirect()

        self.get_user_info()

        return self.redirect()

    def redirect(self):
        url = session.pop('next_url', '/login')
        return redirect(url)

    def get_user_info(self):
        code = Code2Roles.query.filter_by(code=self.code).first()

        if not code:
            return

        session['rols'] = [(k.strip(), k.strip()) for k in code.roles.split(",")]
        db.session.delete(code)
        db.session.commit()


##########
# Routes #
##########
oidc.add_url_rule('/create_client', view_func=CreateClientView.as_view('create_client'))
oidc.add_url_rule('/oauth/authorize', view_func=AuthorizeView.as_view('autorize_oidc'))
oidc.add_url_rule('/allow_code', view_func=AllowCodeView.as_view('allow_code'))
oidc.add_url_rule('/allow_code_oidc4vp', view_func=AllowCodeOidc4vpView.as_view('allow_code_oidc4vp'))
oidc.add_url_rule('/allow_code_oidc4vp2', view_func=AllowCodeOidc4vp2View.as_view('allow_code_oidc4vp2'))
oidc.add_url_rule('/oauth/token', view_func=IssueTokenView.as_view('oauth_issue_token'))
oidc.add_url_rule(
    '/oauth/userinfo', view_func=OauthProfileView.as_view('oauth_user_info')
)
oidc.add_url_rule(
    '/oidc/client/select',
    view_func=SelectInventoryView.as_view('login_other_inventory'),
)