import json
import os
from typing import (
    Any,
    cast,
)

from requests import (
    get,
    put,
)

from galaxy.model.db.user import get_user_by_email
from galaxy_test.driver import integration_util

TEST_USER_EMAIL = "vault_test_user@bx.psu.edu"


class TestExtraUserPreferences(integration_util.IntegrationTestCase, integration_util.ConfiguresDatabaseVault):
    @classmethod
    def handle_galaxy_config_kwds(cls, config):
        super().handle_galaxy_config_kwds(config)
        cls._configure_database_vault(config)
        config["user_preferences_extra_conf_path"] = os.path.join(
            os.path.dirname(__file__), "user_preferences_extra_conf.yml"
        )

    def test_extra_prefs_vault_storage(self):
        user = self._setup_user(TEST_USER_EMAIL)
        url = self.__url("information/inputs", user)
        app = cast(Any, self._test_driver.app if self._test_driver else None)
        db_user = self._get_dbuser(app, user)

        # create some initial data
        put(
            url,
            data=json.dumps(
                {
                    "vaulttestsection|client_id": "hello_client_id",
                    "vaulttestsection|client_secret": "hello_client_secret",
                    "vaulttestsection|refresh_token": "a_super_secret_value",
                }
            ),
        )

        # retrieve saved data
        response = get(url).json()

        def get_input_by_name(inputs, name):
            return [input for input in inputs if input["name"] == name][0]

        inputs = [section for section in response["inputs"] if section["name"] == "vaulttestsection"][0]["inputs"]

        # value should be what we saved
        input_client_id = get_input_by_name(inputs, "client_id")
        assert input_client_id["value"] == "hello_client_id"

        # however, this value should not be in the vault
        assert app.vault.read_secret(f"user/{db_user.id}/preferences/vaulttestsection/client_id") is None
        # it should be in the user preferences model
        assert db_user.extra_preferences["vaulttestsection|client_id"] == "hello_client_id"

        # the secret however, was configured to be stored in the vault
        input_client_secret = get_input_by_name(inputs, "client_secret")
        assert input_client_secret["value"] == "hello_client_secret"
        assert (
            app.vault.read_secret(f"user/{db_user.id}/preferences/vaulttestsection/client_secret")
            == "hello_client_secret"
        )
        # it should not be stored in the user preferences model
        assert db_user.extra_preferences["vaulttestsection|client_secret"] is None

        # secret type values should not be retrievable by the client
        input_refresh_token = get_input_by_name(inputs, "refresh_token")
        assert input_refresh_token["value"] != "a_super_secret_value"
        assert input_refresh_token["value"] == "__SECRET_PLACEHOLDER__"

        # however, that secret value should be correctly stored on the server
        assert (
            app.vault.read_secret(f"user/{db_user.id}/preferences/vaulttestsection/refresh_token")
            == "a_super_secret_value"
        )

    def test_extra_prefs_vault_storage_update_secret(self):
        user = self._setup_user(TEST_USER_EMAIL)
        url = self.__url("information/inputs", user)
        app = cast(Any, self._test_driver.app if self._test_driver else None)
        db_user = self._get_dbuser(app, user)

        # write the initial secret value
        put(
            url,
            data=json.dumps(
                {
                    "vaulttestsection|refresh_token": "a_new_secret_value",
                }
            ),
        )

        # attempt to overwrite it with placeholder
        put(
            url,
            data=json.dumps(
                {
                    "vaulttestsection|refresh_token": "__SECRET_PLACEHOLDER__",
                }
            ),
        )

        # value should not have been overwritten
        assert (
            app.vault.read_secret(f"user/{db_user.id}/preferences/vaulttestsection/refresh_token")
            == "a_new_secret_value"
        )

        # write a new value
        put(
            url,
            data=json.dumps(
                {
                    "vaulttestsection|refresh_token": "an_updated_secret_value",
                }
            ),
        )

        # value should now be overwritten
        assert (
            app.vault.read_secret(f"user/{db_user.id}/preferences/vaulttestsection/refresh_token")
            == "an_updated_secret_value"
        )

    def test_extra_prefs_secret_not_in_vault(self):
        user = self._setup_user(TEST_USER_EMAIL)
        url = self.__url("information/inputs", user)
        app = cast(Any, self._test_driver.app if self._test_driver else None)
        db_user = self._get_dbuser(app, user)

        # create some initial data
        put(
            url,
            data=json.dumps(
                {
                    "non_vault_test_section|user": "test_user",
                    "non_vault_test_section|pass": "test_pass",
                }
            ),
        )

        # retrieve saved data
        response = get(url).json()

        def get_input_by_name(inputs, name):
            return [input for input in inputs if input["name"] == name][0]

        inputs = [section for section in response["inputs"] if section["name"] == "non_vault_test_section"][0]["inputs"]

        # value should be what we saved
        input_user = get_input_by_name(inputs, "user")
        assert input_user["value"] == "test_user"

        # the secret should not be stored in the vault
        assert app.vault.read_secret(f"user/{db_user.id}/preferences/non_vault_test_section/pass") is None
        # it should be in the user preferences model
        app.model.session.refresh(db_user)
        assert db_user.extra_preferences["non_vault_test_section|pass"] == "test_pass"

        # secret type values should not be retrievable by the client
        input_pass = get_input_by_name(inputs, "pass")
        assert input_pass["value"] != "test_pass"
        assert input_pass["value"] == "__SECRET_PLACEHOLDER__"

        # changing the text property value should not change the secret property value
        put(
            url,
            data=json.dumps(
                {
                    "non_vault_test_section|user": "a_new_test_user",
                    "non_vault_test_section|pass": "__SECRET_PLACEHOLDER__",
                }
            ),
        )

        response = get(url).json()
        inputs = [section for section in response["inputs"] if section["name"] == "non_vault_test_section"][0]["inputs"]
        input_user = get_input_by_name(inputs, "user")
        assert input_user["value"] == "a_new_test_user"
        # the secret value is not exposed to the client
        input_pass = get_input_by_name(inputs, "pass")
        assert input_pass["value"] == "__SECRET_PLACEHOLDER__"

        # The secret value should not have changed in the internal user preferences model
        app.model.session.refresh(db_user)
        assert db_user.extra_preferences["non_vault_test_section|pass"] == "test_pass"

        # changing the secret value should update the secret value
        put(
            url,
            data=json.dumps(
                {
                    "non_vault_test_section|user": "a_new_test_user",
                    "non_vault_test_section|pass": "a_new_test_pass",
                }
            ),
        )

        response = get(url).json()
        inputs = [section for section in response["inputs"] if section["name"] == "non_vault_test_section"][0]["inputs"]
        input_pass = get_input_by_name(inputs, "pass")
        assert input_pass["value"] == "__SECRET_PLACEHOLDER__"

        # the secret value should be updated in the internal user preferences model
        app.model.session.refresh(db_user)
        assert db_user.extra_preferences["non_vault_test_section|pass"] == "a_new_test_pass"

    def __url(self, action, user):
        return self._api_url(f"users/{user['id']}/{action}", params=dict(key=self.master_api_key))

    def _get_dbuser(self, app, user):
        return get_user_by_email(app.model.session, user["email"])
