"""High-level entry points for the OpenMetadata Python SDK."""
from __future__ import annotations

import os
from collections.abc import Mapping
from typing import Any, Optional

from metadata.sdk.client import OpenMetadata
from metadata.sdk.config import OpenMetadataConfig
from metadata.sdk.heimdall import HeimdallConfig, HeimdallConfiguration
from metadata.sdk.entities import (
    APICollections,
    APIEndpoints,
    Charts,
    Classifications,
    Containers,
    DashboardDataModels,
    Dashboards,
    DashboardServices,
    Databases,
    DatabaseSchemas,
    DatabaseServices,
    DataContracts,
    DataProducts,
    Domains,
    Glossaries,
    GlossaryTerms,
    Metrics,
    MLModels,
    Pipelines,
    Queries,
    SearchIndexes,
    StoredProcedures,
    Tables,
    Tags,
    Teams,
    TestCases,
    TestDefinitions,
    TestSuites,
    Users,
)
from metadata.sdk.entities.base import BaseEntity

_global_client: Optional[OpenMetadata] = None


def configure(
    config: OpenMetadataConfig | Mapping[str, Any] | None = None,
    /,
    host: str | None = None,
    server_url: str | None = None,
    jwt_token: str | None = None,
    api_key: str | None = None,
    heimdall_url: str | None = None,
    heimdall_configuration: Mapping[str, Any] | None = None,
    **kwargs: Any,
) -> OpenMetadata:
    """Configure the SDK with a connection to OpenMetadata.

    Mirrors the ergonomics of ``stripe.api_key = ...`` by establishing a global
    client that the entity facades rely on. You can either pass an
    :class:`OpenMetadataConfig` instance, a mapping, or keyword arguments.

    If neither config nor keyword arguments are provided, configuration will be
    loaded from environment variables.

    **Heimdall Authentication (recommended)**:
        Use ``heimdall_configuration`` (mirrors hera/config/config.yaml
        authenticationConfiguration.heimdallConfiguration with enabled, baseUrl,
        timeout, fallbackOnBasic) or set ``heimdall_url`` / ``HEIMDALL_BASE_URL``.

    Args:
        config: OpenMetadataConfig instance or mapping
        host: OpenMetadata server URL (alias for server_url). Falls back to
              OPENMETADATA_HOST or OPENMETADATA_SERVER_URL env vars
        server_url: OpenMetadata server URL. Falls back to OPENMETADATA_HOST
                    or OPENMETADATA_SERVER_URL env vars
        jwt_token: Authentication token (JWT or DataOS API key). Falls back to
                   OPENMETADATA_JWT_TOKEN or OPENMETADATA_API_KEY env vars
        api_key: Alias for jwt_token (DataOS API key)
        heimdall_url: Heimdall service base URL (legacy). Falls back to
                      HEIMDALL_BASE_URL env var.
        heimdall_configuration: Heimdall config dict (e.g. enabled, baseUrl,
                                timeout, fallbackOnBasic) matching config.yaml.
        **kwargs: Additional configuration parameters (heimdall_timeout,
                  heimdall_trust_all, verify_ssl, ca_bundle, client_timeout)

    Example:
        Heimdall auth via heimdallConfiguration (recommended)::

            from metadata.sdk import configure
            configure(
                host="http://localhost:8585/api",
                api_key="your-dataos-api-key",
                heimdall_configuration={
                    "enabled": True,
                    "baseUrl": "https://heimdall.example.com/heimdall",
                    "timeout": 10,
                    "fallbackOnBasic": True,
                },
            )

        Or with heimdall_url (legacy)::

            configure(
                host="http://localhost:8585/api",
                api_key="your-dataos-api-key",
                heimdall_url="https://heimdall.example.com/heimdall",
            )

        Environment variables::

            export OPENMETADATA_HOST="http://localhost:8585/api"
            export OPENMETADATA_API_KEY="your-dataos-api-key"
            export HEIMDALL_BASE_URL="https://heimdall.example.com/heimdall"
            from metadata.sdk import configure
            configure()
    """

    global _global_client  # pylint: disable=global-statement

    explicit_params = (
        host or server_url or jwt_token or api_key or heimdall_url
        or heimdall_configuration is not None
        or kwargs
    )
    if config is not None and explicit_params:
        raise TypeError("Pass either a config object or keyword arguments, not both")

    config_obj: OpenMetadataConfig
    if config is None:
        if not explicit_params:
            config_obj = OpenMetadataConfig.from_env()
        else:
            resolved_server_url = (
                host
                or server_url
                or os.environ.get("OPENMETADATA_HOST")
                or os.environ.get("OPENMETADATA_SERVER_URL")
            )
            resolved_token = (
                jwt_token
                or api_key
                or os.environ.get("OPENMETADATA_JWT_TOKEN")
                or os.environ.get("OPENMETADATA_API_KEY")
            )
            resolved_heimdall = (
                heimdall_url or os.environ.get("HEIMDALL_BASE_URL")
            )

            if not resolved_server_url:
                raise ValueError(
                    "Server URL must be provided via 'host'/'server_url' parameter or "
                    + "'OPENMETADATA_HOST'/'OPENMETADATA_SERVER_URL' environment variable"
                )

            config_kw: dict = {**kwargs}
            if heimdall_configuration is not None:
                config_kw["heimdall_configuration"] = heimdall_configuration
            else:
                config_kw.setdefault("heimdall_url", resolved_heimdall)

            config_obj = OpenMetadataConfig(
                server_url=resolved_server_url,
                jwt_token=resolved_token,
                **config_kw,
            )
    elif isinstance(config, Mapping):
        config_dict = dict(config)
        if "heimdallConfiguration" in config_dict and "heimdall_configuration" not in config_dict:
            config_dict["heimdall_configuration"] = config_dict.pop("heimdallConfiguration")
        config_obj = OpenMetadataConfig(**config_dict)
    else:
        config_obj = config

    _global_client = OpenMetadata.initialize(config_obj)
    return _global_client


def client() -> OpenMetadata:
    """Return the active OpenMetadata client."""
    if _global_client is None:
        raise RuntimeError("SDK not configured. Call metadata.sdk.configure(...) first")
    return _global_client


def reset() -> None:
    """Reset the SDK state, closing any cached client."""
    global _global_client  # pylint: disable=global-statement
    OpenMetadata.reset()
    _global_client = None


# Optional lowercase aliases for convenience (similar to stripe.Charge vs stripe.charge)
base_entity = BaseEntity  # pylint: disable=invalid-name
api_collections = APICollections  # pylint: disable=invalid-name
api_endpoints = APIEndpoints  # pylint: disable=invalid-name
charts = Charts  # pylint: disable=invalid-name
classifications = Classifications  # pylint: disable=invalid-name
containers = Containers  # pylint: disable=invalid-name
dashboard_data_models = DashboardDataModels  # pylint: disable=invalid-name
dashboard_services = DashboardServices  # pylint: disable=invalid-name
dashboards = Dashboards  # pylint: disable=invalid-name
data_contracts = DataContracts  # pylint: disable=invalid-name
data_products = DataProducts  # pylint: disable=invalid-name
databases = Databases  # pylint: disable=invalid-name
database_schemas = DatabaseSchemas  # pylint: disable=invalid-name
database_services = DatabaseServices  # pylint: disable=invalid-name
domains = Domains  # pylint: disable=invalid-name
glossaries = Glossaries  # pylint: disable=invalid-name
glossary_terms = GlossaryTerms  # pylint: disable=invalid-name
metrics = Metrics  # pylint: disable=invalid-name
mlmodels = MLModels  # pylint: disable=invalid-name
pipelines = Pipelines  # pylint: disable=invalid-name
queries = Queries  # pylint: disable=invalid-name
search_indexes = SearchIndexes  # pylint: disable=invalid-name
stored_procedures = StoredProcedures  # pylint: disable=invalid-name
tables = Tables  # pylint: disable=invalid-name
tags = Tags  # pylint: disable=invalid-name
teams = Teams  # pylint: disable=invalid-name
test_cases = TestCases  # pylint: disable=invalid-name
test_definitions = TestDefinitions  # pylint: disable=invalid-name
test_suites = TestSuites  # pylint: disable=invalid-name
users = Users  # pylint: disable=invalid-name

__all__ = [
    "OpenMetadata",
    "OpenMetadataConfig",
    "HeimdallConfig",
    "HeimdallConfiguration",
    "configure",
    "reset",
    "client",
    "BaseEntity",
    "base_entity",
    "APICollections",
    "api_collections",
    "APIEndpoints",
    "api_endpoints",
    "Charts",
    "charts",
    "Classifications",
    "classifications",
    "Containers",
    "containers",
    "DashboardDataModels",
    "dashboard_data_models",
    "DashboardServices",
    "dashboard_services",
    "Dashboards",
    "dashboards",
    "DataContracts",
    "data_contracts",
    "DataProducts",
    "data_products",
    "Databases",
    "databases",
    "DatabaseSchemas",
    "database_schemas",
    "DatabaseServices",
    "database_services",
    "Domains",
    "domains",
    "Glossaries",
    "glossaries",
    "GlossaryTerms",
    "glossary_terms",
    "Metrics",
    "metrics",
    "MLModels",
    "mlmodels",
    "Pipelines",
    "pipelines",
    "Queries",
    "queries",
    "SearchIndexes",
    "search_indexes",
    "StoredProcedures",
    "stored_procedures",
    "Tables",
    "tables",
    "Tags",
    "tags",
    "Teams",
    "teams",
    "TestCases",
    "test_cases",
    "TestDefinitions",
    "test_definitions",
    "TestSuites",
    "test_suites",
    "Users",
    "users",
]
