#
#   Copyright © 2022 Evilham <contact@evilham.com>
#
#   This file is part of DD
#
#   DD is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or (at your
#   option) any later version.
#
#   DD 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 DD. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later

import copy
import logging as log
import traceback
from typing import Any, Dict, Tuple

import requests
from attr import define

from admin.lib.keys import ThirdPartyIntegrationKeys

DDUser = Dict[str, Any]


def user_parser(dduser: DDUser) -> DDUser:
    user = copy.deepcopy(dduser)
    user["keycloak_id"] = user.pop("id")
    if "password" in user:
        user.pop("password")
    user["role"] = user["roles"][0] if user.get("roles", []) else user.get("role", None)
    user["groups"] = user.get("groups", user.get("keycloak_groups", []))
    # Compatibility for the API
    for k1, k2 in [("first", "firstname"), ("last", "lastname")]:
        if k2 in user:
            user[k1] = user[k2]
    return user


@define
class ThirdPartyCallbacks:
    """
    If necessary this class may be inherited from and customised.
    By default it uses the configured endpoints to send data to the third-party.
    This data is sent using ThirdPartyIntegrationKeys, which takes care of
    encrypting first, then signing, with the service-specific keys.
    """

    tpkeys: ThirdPartyIntegrationKeys
    """
    The ThirdPartyIntegrationKeys instance where domain and keys are setup.
    """

    endpoint_add_users: Tuple[
        str,
        str,
    ] = ("POST", "/api/users/")
    """
    An HTTP_METHOD, ENDPOINT tuple, where ENDPOINT is an absolute path.
    """

    endpoint_update_users: Tuple[
        str,
        str,
    ] = ("PUT", "/api/users/")
    """
    An HTTP_METHOD, ENDPOINT tuple, where ENDPOINT is an absolute path.
    """

    endpoint_delete_users: Tuple[
        str,
        str,
    ] = ("DELETE", "/api/users/")
    """
    An HTTP_METHOD, ENDPOINT tuple, where ENDPOINT is an absolute path.
    """

    @property
    def add_users_url(self) -> str:
        return f"https://{self.tpkeys.their_service_domain}{self.endpoint_add_users[1]}"

    @property
    def update_users_url(self) -> str:
        return f"https://{self.tpkeys.their_service_domain}{self.endpoint_update_users[1]}"

    @property
    def delete_users_url(self) -> str:
        return f"https://{self.tpkeys.their_service_domain}{self.endpoint_delete_users[1]}"

    def _request(self, method: str, url: str, data: DDUser) -> bool:
        # The endpoints are prepared for batch operations, but the way
        # the admin lib is set up, it is currently not doable.
        prepared_data = [user_parser(data)]
        log.warning(f"  {method} {url} {data.get('id', '?')}")
        try:
            enc_data = self.tpkeys.sign_and_encrypt_outgoing_json(prepared_data)
            headers = self.tpkeys.get_outgoing_request_headers()
            res = requests.request(method, url, data=enc_data, headers=headers)
        except:
            log.error(traceback.format_exc())
            # Something went wrong sending the request
            return False
        return res.status_code == 200

    def add_user(self, user_id: str, user: DDUser) -> bool:
        data = copy.deepcopy(user)
        data["id"] = user_id
        return self._request(self.endpoint_add_users[0], self.add_users_url, data)

    def update_user(self, user_id: str, user: DDUser) -> bool:
        data = copy.deepcopy(user)
        data["id"] = user_id
        return self._request(self.endpoint_update_users[0], self.update_users_url, data)

    def delete_user(self, user_id: str) -> bool:
        data = {"id": user_id}
        return self._request(self.endpoint_delete_users[0], self.delete_users_url, data)
