import json
import os
from enum import Enum
from typing import Dict, TypedDict, Union


class Heap(TypedDict):
    clientId: str


class GoogleAnalytics(TypedDict):
    clientId: str


class Datadog(TypedDict):
    clientToken: str
    service: str


class FeatureFlag(TypedDict):
    clientId: str
    overrides: Dict[str, Union[str, bool]]


class Cloud(TypedDict):
    url: str
    staticContent: str


class Notebooks(TypedDict):
    url: str


class Org(TypedDict):
    url: str


class Environment(str, Enum):
    # Ignore the type errors for now. In the future, once the minimum python is >= 3.11,
    # switch this to StrEnum: https://stackoverflow.com/questions/58608361/string-based-enum-in-python
    development: str = "development"  # type: ignore
    production: str = "production"  # type: ignore
    local: str = "local"  # type: ignore


class AssistantEnvironmentType(str, Enum):
    # Ignore the type errors for now. In the future, once the minimum python is >= 3.11,
    # switch this to StrEnum: https://stackoverflow.com/questions/58608361/string-based-enum-in-python
    # Possible values:
    # - local-dev for local development
    # - cloud-notebooks-prod for users of Anaconda Cloud
    # - local-notebooks-dev for developing the Assistant for users of Jupyter Lab working locally
    # - local-notebooks-prod for users of Jupyter Lab working locally
    # - enterprise-notebooks-dev for developing the Assistant for users of AE5
    # - enterprise-notebooks-prod for users of AE5
    localDev: str = "local-dev"  # type: ignore
    cloudNotebooksProd: str = "cloud-notebooks-prod"  # type: ignore
    localNotebooksDev: str = "local-notebooks-dev"  # type: ignore
    localNotebooksProd: str = "local-notebooks-prod"  # type: ignore
    enterpriseNotebooksDev: str = "enterprise-notebooks-dev"  # type: ignore
    enterpriseNotebooksProd: str = "enterprise-notebooks-prod"  # type: ignore


class AssistantEnvironmentAPIUrl(str, Enum):
    # Ignore the type errors for now. In the future, once the minimum python is >= 3.11,
    # switch this to StrEnum: https://stackoverflow.com/questions/58608361/string-based-enum-in-python
    # Possible values:
    # - https://assistant.anaconda.cloud
    # - https://jupyterassistant.anacondaconnect.com
    production: str = "https://assistant.anaconda.cloud"  # type: ignore
    development: str = "https://jupyterassistant.anacondaconnect.com"  # type: ignore


class AssistantConfig(TypedDict):
    environmentType: AssistantEnvironmentType
    apiUrl: AssistantEnvironmentAPIUrl


class Config(TypedDict):
    environment: Environment
    heap: Heap
    datadog: Datadog
    ga: GoogleAnalytics
    featureFlag: FeatureFlag
    cloud: Cloud
    notebooks: Notebooks
    org: Org
    assistant: AssistantConfig


class ConfigValueFactory:
    """
    A factory for obtaining config values.

    Values can be set by environment variable with a default fallback.
    """

    def __init__(self) -> None:
        try:
            # cloud specific configuration defaults
            # This import will succeed in the cloud package because the associated file in included.
            # The file is not included in the local package and will cause an ImportError.
            # This is handled below to load local specific defaults.
            from .cloud_config_defaults import config_defaults

            if os.environ.get("DEBUG_AEXT_SHARED_LOCAL", False):
                raise ImportError
            self.defaults = config_defaults
        except ImportError:
            # local (non-cloud) configuration defaults
            self.defaults = {
                "ANACONDA_ASSISTANT_ENVIRONMENT_TYPE": "local-notebooks-prod",
                "ANACONDA_ASSISTANT_API_URL": "https://assistant.anaconda.cloud",
                "ANACONDA_CLOUD_API_URL": "https://anaconda.cloud/api",
                "ANACONDA_CLOUD_STATIC_CONTENT_URL": "https://static.anaconda.cloud/shared",
                "ANACONDA_NOTEBOOKS_URL": "https://nb.anaconda.cloud",
                "ANACONDA_ORG_API_URL": "https://notebooks.anaconda.org",
                "DEBUG_FEATURE_FLAG_OVERRIDES": json.dumps({"monorepo-assistant": True}),
                "LOCAL": "true",
            }

    def __getitem__(self, key) -> str:
        return os.environ.get(key, self.defaults.get(key))


def get_config() -> Config:
    cvfactory = ConfigValueFactory()
    return Config(
        heap=Heap(clientId=cvfactory["HEAP_CLIENT_ID"]),
        ga=GoogleAnalytics(clientId=cvfactory["GA_CLIENT_ID"]),
        datadog=Datadog(
            clientToken=cvfactory["DATADOG_CLIENT_TOKEN"],
            service=cvfactory["DATADOG_SERVICE"],
        ),
        featureFlag=FeatureFlag(
            clientId=cvfactory["LAUNCH_DARKLY_CLIENT_ID"],
            overrides=json.loads(cvfactory["DEBUG_FEATURE_FLAG_OVERRIDES"]),
        ),
        cloud=Cloud(
            url=cvfactory["ANACONDA_CLOUD_API_URL"],
            staticContent=cvfactory["ANACONDA_CLOUD_STATIC_CONTENT_URL"],
        ),
        notebooks=Notebooks(url=cvfactory["ANACONDA_NOTEBOOKS_URL"]),
        org=Org(url=cvfactory["ANACONDA_ORG_API_URL"]),
        environment=get_environment(cvfactory),
        assistant=AssistantConfig(
            environmentType=cvfactory["ANACONDA_ASSISTANT_ENVIRONMENT_TYPE"],
            apiUrl=cvfactory["ANACONDA_ASSISTANT_API_URL"],
        ),
    )


def get_environment(cvfactory: ConfigValueFactory) -> Environment:
    if bool(cvfactory["LOCAL"]):
        return Environment.local
    nb_service_site = cvfactory["ANACONDA_NOTEBOOKS_URL"]
    pythonanywhere_site = cvfactory["PYTHONANYWHERE_SITE"]
    if bool(pythonanywhere_site) and pythonanywhere_site in nb_service_site:
        return Environment.production
    return Environment.development
