# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Define forms for generating access tokens and clients."""

from flask import current_app
from flask_wtf import FlaskForm as Form
from invenio_i18n import lazy_gettext as _
from markupsafe import Markup
from oauthlib.oauth2.rfc6749.errors import (
    InsecureTransportError,
    InvalidRedirectURIError,
)
from werkzeug.local import LocalProxy
from wtforms import fields, validators, widgets
from wtforms_alchemy import model_form_factory

from .models import Client
from .theme.semantic.form_styling import SelectSUI
from .validators import URLValidator, validate_redirect_uri


#
# Widget
#
def scopes_multi_checkbox(field, **kwargs):
    """Render multi checkbox widget."""
    kwargs.setdefault("type", "checkbox")
    field_id = kwargs.pop("id", field.id)

    html = ['<div class="row">']

    for value, label, checked, render_kw in field.iter_choices():
        choice_id = "%s-%s" % (field_id, value)

        options = dict(
            kwargs,
            name=field.name,
            value=value,
            id=choice_id,
            class_=" ",
        )

        if checked:
            options["checked"] = "checked"

        html.append('<div class="col-md-3">')
        html.append('<label for="{0}" class="checkbox-inline">'.format(choice_id))
        html.append("<input {0} /> ".format(widgets.html_params(**options)))
        html.append(
            '{0} <br/><small class="text-muted">{1}</small>'.format(
                value, label.help_text
            )
        )
        html.append("</label></div>")
    html.append("</div>")

    return Markup("".join(html))


#
# Redirect URI field
#
class RedirectURIField(fields.TextAreaField):
    """Process redirect URI field data."""

    def process_formdata(self, valuelist):
        """Process form data."""
        if valuelist:
            self.data = "\n".join(
                [
                    x.strip()
                    for x in filter(lambda x: x, "\n".join(valuelist).splitlines())
                ]
            )

    def process_data(self, value):
        """Process data."""
        self.data = "\n".join(value)


class RedirectURIValidator(object):
    """Validate if redirect URIs."""

    def __call__(self, form, field):
        """Call function."""
        errors = []
        for v in field.data.splitlines():
            try:
                validate_redirect_uri(v)
            except InsecureTransportError:
                errors.append(v)
            except InvalidRedirectURIError:
                errors.append(v)

        if errors:
            raise validators.ValidationError(
                _("Invalid redirect URIs: {urls}").format(urls=", ".join(errors))
            )


#
# Forms
#
class ClientFormBase(model_form_factory(Form)):
    """Base class for Client form."""

    class Meta:
        """Metadata class."""

        model = Client
        exclude = [
            "client_secret",
            "is_internal",
        ]
        strip_string_fields = True
        field_args = dict(
            website=dict(
                validators=[validators.DataRequired(), URLValidator()],
                widget=widgets.TextInput(),
            ),
        )


class ClientForm(ClientFormBase):
    """Client form."""

    # Trick to make redirect_uris render in the bottom of the form.
    redirect_uris = RedirectURIField(
        label=_("Redirect URIs (one per line)"),
        description=_(
            "One redirect URI per line. This is your application's"
            " authorization callback URLs. HTTPS must be used for all "
            "hosts except localhost (for testing purposes)."
        ),
        validators=[RedirectURIValidator(), validators.DataRequired()],
        default="",
    )

    widget = LocalProxy(
        lambda: (
            SelectSUI()
            if current_app.config.get("APP_THEME")
            and current_app.config.get("APP_THEME")[0] == "semantic-ui"
            else widgets.Select()
        )
    )

    is_confidential = fields.SelectField(
        label=_("Client type"),
        description=_(
            "Select confidential if your application is capable of keeping "
            "the issued client secret confidential (e.g. a web application), "
            "select public if your application cannot (e.g. a browser-based "
            "JavaScript application). If you select public, your application "
            "MUST validate the redirect URI."
        ),
        coerce=lambda x: False if x == "False" else bool(x),
        choices=[("True", _("Confidential")), ("False", _("Public"))],
        default="True",
        widget=widget,
    )


class TokenForm(Form):
    """Token form."""

    name = fields.StringField(
        description=_("Name of personal access token."),
        validators=[
            validators.DataRequired(),
            validators.Length(
                max=40, message=_("The name must be less than 40 characters long.")
            ),
        ],
    )

    scopes = fields.SelectMultipleField(
        widget=scopes_multi_checkbox,
        choices=[],  # Must be dynamically provided in view.
        description=_(
            "Scopes assign permissions to your personal access token."
            " A personal access token works just like a normal OAuth "
            " access token for authentication against the API."
        ),
    )
