# Copyright 2023 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper to read observability config."""

from dataclasses import dataclass
from dataclasses import field
import json
import os
import pathlib
from typing import Mapping, Optional

GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV = "GRPC_GCP_OBSERVABILITY_CONFIG_FILE"
GRPC_GCP_OBSERVABILITY_CONFIG_ENV = "GRPC_GCP_OBSERVABILITY_CONFIG"


@dataclass
class GcpObservabilityConfig:
    project_id: str = ""
    stats_enabled: bool = False
    tracing_enabled: bool = False
    labels: Optional[Mapping[str, str]] = field(default_factory=dict)
    sampling_rate: Optional[float] = 0.0

    def load_from_string_content(self, config_contents: str) -> None:
        """Loads the configuration from a string.

        Args:
            config_contents: The configuration string.

        Raises:
            ValueError: If the configuration is invalid.
        """
        try:
            config_json = json.loads(config_contents)
        except json.decoder.JSONDecodeError:
            error_msg = "Failed to load Json configuration."
            raise ValueError(error_msg)

        if config_json and not isinstance(config_json, dict):
            error_msg = "Found invalid configuration."
            raise ValueError(error_msg)

        self.project_id = config_json.get("project_id", "")
        self.labels = config_json.get("labels", {})
        self.stats_enabled = "cloud_monitoring" in config_json
        self.tracing_enabled = "cloud_trace" in config_json
        tracing_config = config_json.get("cloud_trace", {})
        self.sampling_rate = tracing_config.get("sampling_rate", 0.0)


def read_config() -> GcpObservabilityConfig:
    """Reads the GCP observability config from the environment variables.

    Returns:
        The GCP observability config.

    Raises:
        ValueError: If the configuration is invalid.
    """
    config_contents = _get_gcp_observability_config_contents()
    config = GcpObservabilityConfig()
    config.load_from_string_content(config_contents)

    if not config.project_id:
        # Get project ID from GCP environment variables since project ID was not
        # set it in the GCP observability config.
        project_id = _get_gcp_project_id_from_env_var()
        if not project_id:
            # Could not find project ID from GCP environment variables either.
            error_msg = "GCP Project ID not found."
            raise ValueError(error_msg)
        config.project_id = project_id
    return config


def _get_gcp_project_id_from_env_var() -> Optional[str]:
    """Gets the project ID from the GCP environment variables.

    Returns:
        The project ID, or an empty string if the project ID could not be found.
    """
    project_id = ""
    project_id = os.getenv("GCP_PROJECT")
    if project_id:
        return project_id

    project_id = os.getenv("GCLOUD_PROJECT")
    if project_id:
        return project_id

    project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
    if project_id:
        return project_id

    return project_id


def _get_gcp_observability_config_contents() -> str:
    """Get the contents of the observability config from environment variable or file.

    Returns:
        The content from environment variable.

    Raises:
        ValueError: If no configuration content was found.
    """
    contents_str = ""
    # First try get config from GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV.
    config_path = os.getenv(GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV)
    if config_path:
        with pathlib.Path(config_path).open("r") as f:
            contents_str = f.read()

    # Next, try GRPC_GCP_OBSERVABILITY_CONFIG_ENV env var.
    if not contents_str:
        contents_str = os.getenv(GRPC_GCP_OBSERVABILITY_CONFIG_ENV)

    if not contents_str:
        error_msg = "Configuration content not found."
        raise ValueError(error_msg)

    return contents_str
