"""Settings manager for Meltano plugins."""

from __future__ import annotations

from typing import Any

from meltano.core.plugin.project_plugin import ProjectPlugin
from meltano.core.project import Project
from meltano.core.project_plugins_service import ProjectPluginsService
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.setting_definition import SettingDefinition
from meltano.core.settings_service import FeatureFlags, SettingsService
from meltano.core.utils import expand_env_vars


class PluginSettingsService(SettingsService):
    """Settings manager for Meltano plugins."""

    def __init__(
        self,
        project: Project,
        plugin: ProjectPlugin,
        *args,
        plugins_service: ProjectPluginsService = None,
        **kwargs,
    ):
        """Create a new plugin settings manager.

        Args:
            project: The Meltano project.
            plugin: The Meltano plugin.
            args: Positional arguments to pass to the superclass.
            plugins_service: The Meltano plugins service.
            kwargs: Keyword arguments to pass to the superclass.
        """
        super().__init__(project, *args, **kwargs)

        self.plugin = plugin
        self.plugins_service = plugins_service or ProjectPluginsService(self.project)

        self._inherited_settings_service = None
        if self.project.active_environment:
            environment = self.project.active_environment
            self.environment_plugin_config = environment.get_plugin_config(
                self.plugin.type,
                self.plugin.name,
            )
        else:
            self.environment_plugin_config = None

        project_settings_service = ProjectSettingsService(
            self.project, config_service=self.plugins_service.config_service
        )

        self.env_override = {
            **project_settings_service.env,  # project level environment variables
            **project_settings_service.as_env(),  # project level settings as env vars (e.g. MELTANO_PROJECT_ID)
            **self.env_override,  # plugin level overrides, passed in as **kwargs and set to self.env_overrides by super().__init__ above
            **self.plugin.info_env,  # generated generic plugin settings as env vars (e.g. MELTANO_EXTRACT_NAME)
            **self.plugin.env,  # env vars stored under the `env:` key of the plugin definition
        }

        environment_env = {}
        if self.project.active_environment:
            with self.feature_flag(
                FeatureFlags.STRICT_ENV_VAR_MODE, raise_error=False
            ) as strict_env_var_mode:
                environment_env = {
                    var: expand_env_vars(
                        value,
                        self.env_override,
                        raise_if_missing=strict_env_var_mode,
                    )
                    for var, value in self.project.active_environment.env.items()
                }
            self.env_override.update(
                environment_env
            )  # active Meltano Environment top level `env:` key

        environment_plugin_env = (
            self.environment_plugin_config.env if self.environment_plugin_config else {}
        )
        self.env_override.update(
            environment_plugin_env
        )  # env vars stored under the `env:` key of the plugin definition of the active meltano Environment

    @property
    def label(self):
        """Get the label for this plugin.

        Returns:
            The label for this plugin.
        """
        return f"{self.plugin.type.descriptor} '{self.plugin.name}'"  # noqa: WPS237

    @property
    def docs_url(self):
        """Get the documentation URL for this plugin.

        Returns:
            The documentation URL for this plugin.
        """
        return self.plugin.docs

    def setting_env_vars(self, setting_def: SettingDefinition, for_writing=False):
        """Get environment variables for a setting.

        Args:
            setting_def: The setting definition.
            for_writing: Whether to get environment variables for writing.

        Returns:
            Environment variables for a setting.
        """
        return setting_def.env_vars(
            prefixes=self.plugin.env_prefixes(for_writing=for_writing),
            include_custom=self.plugin.is_shadowing or for_writing,
            for_writing=for_writing,
        )

    @property
    def db_namespace(self):
        """Return namespace for setting value records in system database.

        Returns:
            Namespace for setting value records in system database.
        """
        # "default" is included for legacy reasons
        return ".".join((self.plugin.type, self.plugin.name, "default"))

    @property
    def setting_definitions(self) -> list[SettingDefinition]:
        """Return definitions of supported settings.

        Returns:
            A list of setting definitions.
        """
        settings = self.plugin.settings_with_extras

        if self.environment_plugin_config is not None:
            settings.extend(
                self.environment_plugin_config.get_orphan_settings(settings)
            )

        return settings

    @property
    def meltano_yml_config(self):
        """Return current configuration in `meltano.yml`.

        Returns:
            Current configuration in `meltano.yml`.
        """
        return self.plugin.config_with_extras

    @property
    def environment_config(self):
        """Return current environment configuration in `meltano.yml`.

        Returns:
            Current environment configuration in `meltano.yml`.
        """
        if self.environment_plugin_config:
            return self.environment_plugin_config.config_with_extras
        return {}

    def update_meltano_yml_config(self, config_with_extras):
        """Update configuration in `meltano.yml`.

        Args:
            config_with_extras: Configuration to update.
        """
        self.plugin.config_with_extras = config_with_extras
        self.plugins_service.update_plugin(self.plugin)

    def update_meltano_environment_config(self, config_with_extras: dict[str, Any]):
        """Update environment configuration in `meltano.yml`.

        Args:
            config_with_extras: Configuration to update.
        """
        self.environment_plugin_config.config_with_extras = config_with_extras
        self.plugins_service.update_environment_plugin(self.environment_plugin_config)

    @property
    def inherited_settings_service(self):
        """Return settings service to inherit configuration from.

        Returns:
            Settings service to inherit configuration from.
        """
        parent_plugin = self.plugin.parent
        if not isinstance(parent_plugin, ProjectPlugin):
            return None

        if self._inherited_settings_service is None:
            self._inherited_settings_service = self.__class__(
                self.project,
                parent_plugin,
                env_override=self.env_override,
                plugins_service=self.plugins_service,
            )
        return self._inherited_settings_service

    def process_config(self, config):
        """Process configuration dictionary to be passed to plugin.

        Args:
            config: Configuration dictionary to process.

        Returns:
            Processed configuration dictionary.
        """
        return self.plugin.process_config(config)

src/meltano/core/plugin/settings_service.py
"""Settings manager for Meltano plugins."""

from __future__ import annotations

from typing import Any

from meltano.core.plugin.project_plugin import ProjectPlugin
from meltano.core.project import Project
from meltano.core.project_plugins_service import ProjectPluginsService
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.setting_definition import SettingDefinition
from meltano.core.settings_service import FeatureFlags, SettingsService
from meltano.core.utils import expand_env_vars


class PluginSettingsService(SettingsService):
    """Settings manager for Meltano plugins."""

    def __init__(
        self,
        project: Project,
        plugin: ProjectPlugin,
        *args,
        plugins_service: ProjectPluginsService = None,
        **kwargs,
    ):
        """Create a new plugin settings manager.

        Args:
            project: The Meltano project.
            plugin: The Meltano plugin.
            args: Positional arguments to pass to the superclass.
            plugins_service: The Meltano plugins service.
            kwargs: Keyword arguments to pass to the superclass.
        """
        super().__init__(project, *args, **kwargs)

        self.plugin = plugin
        self.plugins_service = plugins_service or ProjectPluginsService(self.project)

        self._inherited_settings_service = None
        if self.project.active_environment:
            environment = self.project.active_environment
            self.environment_plugin_config = environment.get_plugin_config(
                self.plugin.type,
                self.plugin.name,
            )
        else:
            self.environment_plugin_config = None

        project_settings_service = ProjectSettingsService(
            self.project, config_service=self.plugins_service.config_service
        )

        self.env_override = {
            **project_settings_service.env,  # project level environment variables
            **project_settings_service.as_env(),  # project level settings as env vars (e.g. MELTANO_PROJECT_ID)
            **self.env_override,  # plugin level overrides, passed in as **kwargs and set to self.env_overrides by super().__init__ above
            **self.plugin.info_env,  # generated generic plugin settings as env vars (e.g. MELTANO_EXTRACT_NAME)
            **self.plugin.env,  # env vars stored under the `env:` key of the plugin definition
        }

        environment_env = {}
        if self.project.active_environment:
            with project_settings_service.feature_flag(
                FeatureFlags.STRICT_ENV_VAR_MODE, raise_error=False
            ) as strict_env_var_mode:
                environment_env = {
                    var: expand_env_vars(
                        value,
                        self.env_override,
                        raise_if_missing=strict_env_var_mode,
                    )
                    for var, value in self.project.active_environment.env.items()
                }
            self.env_override.update(
                environment_env
            )  # active Meltano Environment top level `env:` key

        environment_plugin_env = (
            self.environment_plugin_config.env if self.environment_plugin_config else {}
        )
        self.env_override.update(
            environment_plugin_env
        )  # env vars stored under the `env:` key of the plugin definition of the active meltano Environment

    @property
    def label(self):
        """Get the label for this plugin.

        Returns:
            The label for this plugin.
        """
        return f"{self.plugin.type.descriptor} '{self.plugin.name}'"  # noqa: WPS237

    @property
    def docs_url(self):
        """Get the documentation URL for this plugin.

        Returns:
            The documentation URL for this plugin.
        """
        return self.plugin.docs

    def setting_env_vars(self, setting_def: SettingDefinition, for_writing=False):
        """Get environment variables for a setting.

        Args:
            setting_def: The setting definition.
            for_writing: Whether to get environment variables for writing.

        Returns:
            Environment variables for a setting.
        """
        return setting_def.env_vars(
            prefixes=self.plugin.env_prefixes(for_writing=for_writing),
            include_custom=self.plugin.is_shadowing or for_writing,
            for_writing=for_writing,
        )

    @property
    def db_namespace(self):
        """Return namespace for setting value records in system database.

        Returns:
            Namespace for setting value records in system database.
        """
        # "default" is included for legacy reasons
        return ".".join((self.plugin.type, self.plugin.name, "default"))

    @property
    def setting_definitions(self) -> list[SettingDefinition]:
        """Return definitions of supported settings.

        Returns:
            A list of setting definitions.
        """
        settings = self.plugin.settings_with_extras

        if self.environment_plugin_config is not None:
            settings.extend(
                self.environment_plugin_config.get_orphan_settings(settings)
            )

        return settings

    @property
    def meltano_yml_config(self):
        """Return current configuration in `meltano.yml`.

        Returns:
            Current configuration in `meltano.yml`.
        """
        return self.plugin.config_with_extras

    @property
    def environment_config(self):
        """Return current environment configuration in `meltano.yml`.

        Returns:
            Current environment configuration in `meltano.yml`.
        """
        if self.environment_plugin_config:
            return self.environment_plugin_config.config_with_extras
        return {}

    def update_meltano_yml_config(self, config_with_extras):
        """Update configuration in `meltano.yml`.

        Args:
            config_with_extras: Configuration to update.
        """
        self.plugin.config_with_extras = config_with_extras
        self.plugins_service.update_plugin(self.plugin)

    def update_meltano_environment_config(self, config_with_extras: dict[str, Any]):
        """Update environment configuration in `meltano.yml`.

        Args:
            config_with_extras: Configuration to update.
        """
        self.environment_plugin_config.config_with_extras = config_with_extras
        self.plugins_service.update_environment_plugin(self.environment_plugin_config)

    @property
    def inherited_settings_service(self):
        """Return settings service to inherit configuration from.

        Returns:
            Settings service to inherit configuration from.
        """
        parent_plugin = self.plugin.parent
        if not isinstance(parent_plugin, ProjectPlugin):
            return None

        if self._inherited_settings_service is None:
            self._inherited_settings_service = self.__class__(
                self.project,
                parent_plugin,
                env_override=self.env_override,
                plugins_service=self.plugins_service,
            )
        return self._inherited_settings_service

    def process_config(self, config):
        """Process configuration dictionary to be passed to plugin.

        Args:
            config: Configuration dictionary to process.

        Returns:
            Processed configuration dictionary.
        """
        return self.plugin.process_config(config)

src/meltano/core/plugin/settings_service.py
diff --git a/.gitignore b/.gitignore
index 57d5a70cb5..9e194d8f0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ api/parser
 *.ntvs*
 *.njsproj
 *.sln
+.sourcery.yaml
 
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/docs/example-library/meltano-objects/ending-meltano.yml b/docs/example-library/meltano-objects/ending-meltano.yml
index 18b413b746..97c110be2e 100644
--- a/docs/example-library/meltano-objects/ending-meltano.yml
+++ b/docs/example-library/meltano-objects/ending-meltano.yml
@@ -7,10 +7,6 @@ environments:
 - name: qa
 - name: prod
 plugins:
-  extractors:
-  - name: tap-gitlab
-    variant: meltanolabs
-    pip_url: git+https://github.com/MeltanoLabs/tap-gitlab.git
   loaders:
   - name: target-jsonl
     variant: andyh1203
diff --git a/docs/example-library/meltano-objects/index.md b/docs/example-library/meltano-objects/index.md
index 014af8daf8..ffa7abaa0c 100644
--- a/docs/example-library/meltano-objects/index.md
+++ b/docs/example-library/meltano-objects/index.md
@@ -31,3 +31,9 @@ meltano job add gitlab_to_jsonl --tasks '["tap-gitlab target-jsonl"]'
 ```shell
 meltano schedule add run_gitlab --job gitlab_to_jsonl --interval "@daily"
 ```
+
+## Remove a plugin
+
+```shell
+meltano remove extractor tap-gitlab
+```
diff --git a/src/meltano/core/plugin/settings_service.py b/src/meltano/core/plugin/settings_service.py
index 0dc9102fa4..a63caa4aed 100644
--- a/src/meltano/core/plugin/settings_service.py
+++ b/src/meltano/core/plugin/settings_service.py
@@ -62,7 +62,7 @@ def __init__(
 
         environment_env = {}
         if self.project.active_environment:
-            with self.feature_flag(
+            with project_settings_service.feature_flag(
                 FeatureFlags.STRICT_ENV_VAR_MODE, raise_error=False
             ) as strict_env_var_mode:
                 environment_env = {

bug:  Regression in `meltano remove`
### Meltano Version

2.3.0

### Python Version

NA

### Bug scope

CLI (options, error messages, logging, etc.)

### Operating System

NA

### Description

Meltano `2.3.0` introduced a regression on `meltano remove` where it complains that the plugin is missing the `settings_with_extras` attribute.

The regression was most likely introduced in https://github.com/meltano/meltano/pull/6267 somewhere in the use of a feature flag:

https://github.com/meltano/meltano/blob/23a9099cefe148cb560d25e7ae335447858948df/src/meltano/core/plugin/settings_service.py#L63-L65

I still haven't figured out the root cause but might be related with trying to check for feature flags in the `PluginSettingsService`.

Other commands may be similarly affected, but at least `invoke` and `run` seem to be working fine.

### Code


<details><summary>Traceback</summary>

```
Traceback (most recent call last):
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/behavior/canonical.py", line 138, in __getattr__
    value = self._dict[attr]
KeyError: 'settings_with_extras'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/__init__.py", line 66, in _run_cli
    cli(obj={"project": None})
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/cli.py", line 35, in main
    return super().main(*args, windows_expand_args=False, **kwargs)
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/utils.py", line 540, in invoke
    super().invoke(ctx)  # noqa: WPS608
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/params.py", line 18, in decorate
    return func(*args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/params.py", line 53, in decorate
    func(project, *args, **kwargs)
  File "/Users/edgarramirez/Library/Caches/pypoetry/virtualenvs/meltano-SY7IjDqw-py3.9/lib/python3.9/site-packages/click/decorators.py", line 26, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/remove.py", line 32, in remove
    remove_plugins(project, plugins)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/remove.py", line 39, in remove_plugins
    num_removed, total = remove_service.remove_plugins(
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/plugin_remove_service.py", line 60, in remove_plugins
    removal_managers = self.remove_plugin(plugin)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/plugin_remove_service.py", line 87, in remove_plugin
    DbRemoveManager(plugin, self.project),
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/plugin_location_remove.py", line 86, in __init__
    self.plugins_settings_service = PluginSettingsService(project, plugin)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/plugin/settings_service.py", line 63, in __init__
    with self.feature_flag(
  File "/Users/edgarramirez/.pyenv/versions/3.9.13/lib/python3.9/contextlib.py", line 119, in __enter__
    return next(self.gen)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 657, in feature_flag
    allowed = self.get(f"{FEATURE_FLAG_PREFIX}.{feature}") or False
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 444, in get
    value, _ = self.get_with_source(*args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 431, in get_with_source
    value, metadata = self.get_with_metadata(*args, **kwargs)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 333, in get_with_metadata
    setting_def = setting_def or self.find_setting(name)
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 598, in find_setting
    for setting in self.definitions()
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/settings_service.py", line 568, in definitions
    for setting in self.setting_definitions
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/plugin/settings_service.py", line 136, in setting_definitions
    settings = self.plugin.settings_with_extras
  File "/Users/edgarramirez/meltano/meltano/src/meltano/core/behavior/canonical.py", line 143, in __getattr__
    raise AttributeError(attr) from err
AttributeError: settings_with_extras

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/edgarramirez/meltano/meltano/src/meltano/cli/__init__.py", line 74, in _run_cli
    raise CliError(str(err)) from err
meltano.cli.utils.CliError: settings_with_extras
settings_with_extras
```

</details>


