#!/usr/bin/env python3
# coding: utf-8
# pyright: reportMissingImports=false
# pyright: reportOptionalOperand=false
# pyright: reportOptionalSubscript=false
# pylint: disable=import-error
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-public-methods

"""Passbolt"""

import json
import os
import re
import warnings
from pprint import pprint
from urllib.parse import unquote

import gnupg
import httpx
from pgpy import PGPKey, PGPMessage

warnings.filterwarnings(
    "ignore",
    ".*deprecated.*",
)


# https://pgpy.readthedocs.io/en/latest/examples.html


class PassboltAPI:
    """Passbolt API Class"""

    def __init__(
        self,
        dict_config=None,
    ):

        self.load_config(
            dict_config=dict_config,
        )

        # Load key
        if self.config.get("gpg_library", "PGPy") == "gnupg":
            self.gpg = gnupg.GPG(gpgbinary=self.config.get("gpgbinary", "gpg"))
            self.fingerprint = self.config.get("fingerprint", "")
        else:
            self.key, _ = PGPKey.from_blob(self.config.get("private_key"))
            self.fingerprint = self.key.fingerprint.replace(" ", "")
        self.base_url = self.config.get("base_url")
        self.login_url = f"{self.base_url}/auth/login.json"
        self.users_url = f"{self.base_url}/users.json"
        self.me_url = f"{self.base_url}/users/me.json"
        self.groups_url = f"{self.base_url}/groups.json"
        self.verify = self.config.get("verify", True)
        self.timeout = self.config.get("timeout", 5.0)

        # vars definition
        self.authenticated = False
        self.token = None
        self.user_id = None
        self.pgp_message = None
        self.nonce = None

        self.session = httpx.Client(verify=self.verify, timeout=float(self.timeout))
        self.cookies = httpx.Cookies()

        self.login()

        # resource types
        self.resource_types = self.get_resource_type_ids()

    def load_config(
        self,
        dict_config=None,
    ):
        """Load Config"""

        if dict_config:
            self.config = dict_config
        else:
            self.config = {
                "gpg_binary": os.environ.get("PASSBOLT_GPG_BINARY", "gpg"),
                "gpg_library": os.environ.get("PASSBOLT_GPG_LIBRARY", "PGPy"),
                "base_url": os.environ.get("PASSBOLT_BASE_URL", "https://undefined"),
                "private_key": os.environ.get("PASSBOLT_PRIVATE_KEY", "undefined"),
                "passphrase": os.environ.get("PASSBOLT_PASSPHRASE", "undefined"),
                "fingerprint": os.environ.get("PASSBOLT_FINGERPRINT", "undefined"),
                "verify": os.environ.get("PASSBOLT_VERIFY", True),
                "timeout": os.environ.get("PASSBOLT_TIMEOUT", 5.0),
            }

    def stage1(self):
        """Stage 1"""

        post = {"data": {"gpg_auth": {"keyid": self.fingerprint}}}

        response = self.session.post(self.login_url, json=post)
        decoded_response = json.loads(response.text)

        if decoded_response["header"]["code"] == 200:
            pgp_message = unquote(
                response.headers.get("x-gpgauth-user-auth-token")
            ).replace("\\+", " ")
            return pgp_message

        pprint(decoded_response)
        return None

    def decrypt(self, message):
        """Decrypt"""

        if self.config.get("gpg_library", "PGPy") == "gnupg":
            decrypt = self.gpg.decrypt(message)
            return decrypt

        pgp_message = PGPMessage.from_blob(message)
        with self.key.unlock(self.config.get("passphrase")):
            return self.key.decrypt(pgp_message).message

    def encrypt(self, message, public_key):
        """
        Encrypt a message for a given public key

        :param message: message to encrypt
        :type message: str
        :public_key: passbolt gpgkey object
        :type public_key: dict
        :returns: Encrypted message
        :rtype: str
        """
        if isinstance(message, dict):
            message = json.dumps(message)
        if self.config.get("gpg_library", "PGPy") == "gnupg":
            self.gpg.import_keys(public_key["armored_key"])
            encrypt = self.gpg.encrypt(
                message,
                public_key["fingerprint"],
                always_trust=True,
                sign=self.fingerprint,
            )
            return str(encrypt)

        pubkey, _ = PGPKey.from_blob(public_key["armored_key"])
        pgp_message = PGPMessage.new(message)
        with self.key.unlock(self.config.get("passphrase")):
            pgp_message |= self.key.sign(pgp_message)
        return str(pubkey.encrypt(pgp_message))

    def stage2(self, nonce):
        """Stage 2"""

        post = {
            "data": {
                "gpg_auth": {"keyid": self.fingerprint, "user_token_result": nonce}
            }
        }

        response = self.session.post(self.login_url, json=post)
        decoded_response = json.loads(response.text)

        if decoded_response["header"]["code"] == 200:
            return True

        return False

    def get_token(self, cookie):
        """Get Token"""

        pattern = r"csrfToken=([^;]+)"
        match = re.search(pattern, cookie)
        if match is not None:
            return match.group(1)

        return match

    def get_cookie(self):
        """Get Cookie"""

        response = self.session.get(self.me_url)
        cookie = response.headers.get("set-cookie")
        user_id = json.loads(response.text)
        self.user_id = user_id["body"]["id"]
        self.token = self.get_token(cookie)
        self.session.headers = {"X-CSRF-Token": self.token}

    def check_login(self):
        """Check Login"""

        response = self.session.get(self.base_url + "/")
        if response.status_code != 200:
            print("Falha no login")
            print(response)

    def login(self):
        """Login"""

        self.pgp_message = self.stage1()
        if self.config.get("gpg_library", "PGPy") == "gnupg":
            self.nonce = self.decrypt(self.pgp_message)
        else:
            self.nonce = self.decrypt(self.pgp_message).decode()
        self.authenticated = self.stage2(str(self.nonce))
        self.get_cookie()
        self.check_login()

    def get_users(self):
        """Get Users"""

        response = self.session.get(self.users_url)
        decoded_response = json.loads(response.text)
        return decoded_response["body"]

    def get_groups(self):
        """Get Groups"""

        response = self.session.get(self.groups_url)
        decoded_response = json.loads(response.text)
        return decoded_response["body"]

    def get_user_by_email(self, email):
        """Get Users By Email"""

        users = self.get_users()
        for user in users:
            if user["username"] == email:
                return user

        return None

    def get_user_by_id(self, user_id):
        """Get Users By Id"""
        users = self.get_users()
        for user in users:
            if user["id"] == user_id:
                return user

        return None

    def get_group_by_name(self, group_name):
        """Get Group By Name"""

        groups = self.get_groups()
        for group in groups:
            if group["name"] == group_name:
                return group

        return None

    def create_group(self, group_name):
        """Create Group"""

        post = {
            "name": group_name,
            "groups_users": [{"user_id": self.user_id, "is_admin": True}],
        }

        response = self.session.post(self.groups_url, json=post)

        return response

    def put_user_on_group(self, group_id, user_id, admin=False):
        """Put User On Group"""

        post = {
            "id": group_id,
            "groups_users": [{"user_id": user_id, "is_admin": admin}],
        }
        url = f"{self.base_url}/groups/{group_id}/dry-run.json"
        response = self.session.put(url, json=post)
        if response.status_code == 200:
            user_key = self.get_user_public_key(user_id)
            secrets = json.loads(response.text)["body"]["dry-run"]["Secrets"]

            secrets_list = []
            for secret in secrets:
                decrypted = self.decrypt(secret["Secret"][0]["data"])
                reencrypted = self.encrypt(str(decrypted), user_key)

                secrets_list.append(
                    {
                        "resource_id": secret["Secret"][0]["resource_id"],
                        "user_id": user_id,
                        "data": str(reencrypted),
                    }
                )

            post = {
                "id": group_id,
                "groups_users": [{"user_id": user_id, "is_admin": admin}],
                "secrets": secrets_list,
            }

            url = f"{self.base_url}/groups/{group_id}.json"
            response = self.session.put(url, json=post)
        else:
            print(response.headers)
            print()
            print(response.text)
            print()

        return response

    def get_group_by_id(self, group_id):
        """Get Group By Id"""

        groups = self.get_groups()
        for group in groups:
            if group["id"] == group_id:
                return group

        return None

    def get_group_user_id(self, group_id, user_id):
        """Get Group User Id"""

        user = self.get_user_by_id(user_id)
        for group in user["groups_users"]:
            if group["group_id"] == group_id:
                return group["id"]

        return None

    def update_user_to_group_admin(self, group_id, user_id):
        """Update User To Group Admin"""

        group_user_id = self.get_group_user_id(group_id, user_id)

        post = {
            "id": group_id,
            "groups_users": [{"id": group_user_id, "is_admin": True}],
        }
        url = f"{self.base_url}/groups/{group_id}/dry-run.json"
        response = self.session.put(url, json=post)
        if response.status_code == 200:
            url = f"{self.base_url}/groups/{group_id}.json"
            response = self.session.put(url, json=post)
        else:
            print(response.headers)
            print()
            print(response.text)
            print()

        return response

    def get_user_public_key(self, user_id):
        """
        Return a gpgkey dictionary based on passbolt user_id parameter

        {'armored_key': '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n'
                        'Version: OpenPGP.js v4.10.9\r\n'
                        'Comment: https:\\/\\/openpgpjs.org\r\n'
                        '\r\n'
                        '-----END PGP PUBLIC KEY BLOCK-----\r\n',
         'bits': 2048,
         'created': '2022-01-14T10:19:30+00:00',
         'deleted': False,
         'expires': None,
         'fingerprint': '1321159AE7BEE9EF9C4BBC7ECBAD2FB0C22FE70C',
         'id': '9a339ea9-ce31-40cc-8b66-f592e3d5acfa',
         'key_created': '2022-01-14T10:19:24+00:00',
         'key_id': 'C22FE70C',
         'modified': '2022-01-14T10:19:30+00:00',
         'type': 'RSA',
         'uid': 'John Doe <johndoe@domain.tld>',
         'user_id': 'd9143b6d-82bb-4093-9272-6471e222e990'}

        :param user_id: The passbolt user_id we want to retrieve
        :type user_id: str
        :returns: gpgkey dictionary
        :rtype: dict
        """

        url = f"{self.base_url}/users/{user_id}.json"
        response = self.session.get(url)

        user = json.loads(response.text)["body"]
        return user["gpgkey"]

    def get_resource_secret(self, resource_id):
        """Get Resource Secret"""

        url = f"{self.base_url}/secrets/resource/{resource_id}.json"
        response = self.session.get(url)

        secrete_data = json.loads(response.text)["body"]["data"]
        return secrete_data

    def get_resources(self):
        """Get Resources"""

        url = f"{self.base_url}/resources.json"
        response = self.session.get(url)

        secrete_data = json.loads(response.text)["body"]
        return secrete_data

    def get_resource_per_uuid(self, uuid):
        """Get Resource Per UUID"""

        url = f"{self.base_url}/resources/{uuid}.json"
        response = self.session.get(url)

        secrete_data = json.loads(response.text)["body"]
        return secrete_data

    def create_resource(self, resource):
        """Create Resource"""

        return self.session.post(f"{self.base_url}/resources.json", json=resource)

    def get_resource_types(self):
        """
        Returns all available resource types. Currently 2 resources types available:
        * password-string
        * password-and-description

        :returns: available resources types
        :rtype: dict
        """

        response = self.session.get(f"{self.base_url}/resource-types.json")
        decoded_response = json.loads(response.text)
        return decoded_response["body"]

    def get_resource_type_ids(self, per="slug"):
        """Get Resource Type Ids"""

        res = {}
        for item in self.get_resource_types():
            res[item[per]] = item["id"]
        return res
