# -*- coding: utf-8 -*-
#
# License:  AGPLv3
# This file is part of eduMFA. eduMFA is a fork of privacyIDEA which was forked from LinOTP.
# Copyright (c) 2024 eduMFA Project-Team
# Previous authors by privacyIDEA project:
#
# 2019 Cornelius Kölbel <cornelius.koelbel@netknights.it>
#
# (c) 2019. Cornelius Kölbel
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
# License as published by the Free Software Foundation; either
# version 3 of the License, or any later version.
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU AFFERO GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
__doc__ = """This is the event handler module modifying request parameters.
"""
import datetime
import json
import logging
import re

import yaml
from dateutil.parser import parse as parse_date_string
from dateutil.tz import tzlocal

from edumfa.lib import _
from edumfa.lib.eventhandler.base import BaseEventHandler
from edumfa.lib.realm import get_realms
from edumfa.lib.token import (
    add_tokeninfo,
    delete_tokeninfo,
    enable_token,
    get_token_types,
    init_token,
    remove_token,
    set_count_window,
    set_description,
    set_failcounter,
    set_realms,
    set_validity_period_end,
    set_validity_period_start,
    unassign_token,
)
from edumfa.lib.tokenclass import AUTH_DATE_FORMAT, DATE_FORMAT
from edumfa.lib.utils import is_true, parse_date, parse_time_offset_from_now

log = logging.getLogger(__name__)


class ACTION_TYPE:
    """
    Allowed actions
    """

    SET = "set"
    DELETE = "delete"


class RequestManglerEventHandler(BaseEventHandler):
    """
    An Eventhandler needs to return a list of actions, which it can handle.

    It also returns a list of allowed action and conditions

    It returns an identifier, which can be used in the eventhandlig definitions
    """

    identifier = "RequestMangler"
    description = "This event handler can modify the parameters in the request."

    @property
    def allowed_positions(cls):
        """
        This returns the allowed positions of the event handler definition.
        :return: list of allowed positions
        """
        """
        Usually we would only modify the parameters in the PRE location, so that
        the request is handled with the modified parameters. 
        
        But we could also modify the parameters in the POST location, so that the request
        is handled with the original parameters, but *after* the request is handled,
        some parameters can be changed to that an event handler, that is called *After*
        the RequestMangler gets other input parameters.
        
        At the time of writing I can not make up a scenario, but technically it could 
        make sense.
        """
        return ["post", "pre"]

    @property
    def actions(cls):
        """
        This method returns a dictionary of allowed actions and possible
        options in this handler module.

        :return: dict with actions
        """
        actions = {
            ACTION_TYPE.DELETE: {
                "parameter": {
                    "type": "str",
                    "required": True,
                    "description": _("The parameter that should be deleted."),
                }
            },
            ACTION_TYPE.SET: {
                "parameter": {
                    "type": "str",
                    "required": True,
                    "description": _("The parameter that should be added or modified."),
                },
                "value": {
                    "type": "str",
                    "required": True,
                    "description": _(
                        "The new value of the parameter. Can contain tags like {0}, {1} for "
                        "the matched sub strings."
                    ),
                },
                "match_parameter": {
                    "type": "str",
                    "description": _("The parameter, that should match some values."),
                },
                "match_pattern": {
                    "type": "str",
                    "description": _(
                        "The value of the match_parameter. It can contain a regular "
                        "expression and '()' to transfer values to the new parameter."
                    ),
                },
            },
        }
        return actions

    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        request = options.get("request")
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})
        parameter = handler_options.get("parameter")

        if parameter:
            if action.lower() == ACTION_TYPE.DELETE:
                if parameter in request.all_data:
                    del request.all_data[parameter]
            elif action.lower() == ACTION_TYPE.SET:
                value = handler_options.get("value")
                match_parameter = handler_options.get("match_parameter")
                match_pattern = handler_options.get("match_pattern")
                if value is not None:
                    # We only take action, if we have a value, even an empty string "".
                    if not match_parameter:
                        # simple setting a parameter
                        request.all_data[parameter] = value
                    elif match_pattern and match_parameter in request.all_data:
                        # setting a parameter depending on another value,
                        # but only set it, if match_parameter exists
                        """
                        Note: Beware user supplied  format-string like "match_pattern", it can
                        be dangerous: http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
                        but in our case it is fine because no objects are involved, as m.groups() 
                        always returns a tuple of strings
                        """
                        m = re.match(
                            "^" + match_pattern + "$",
                            request.all_data.get(match_parameter),
                        )
                        if m:
                            # Now we set the new value with the matching tuple
                            try:
                                request.all_data[parameter] = value.format(*m.groups())
                            except IndexError:
                                log.warning(
                                    "The number of found tags ({0!r}) "
                                    "do not match the required number ({1!r}).".format(
                                        m.groups(), value
                                    )
                                )

        return ret
