# Copyright 2024 Klira AI
#
# 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.

"""Centralized KliraConfig — immutable after construction (Learning #15)."""

from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from typing import Any

from klira.sdk.contracts.phi_pipeline import PhiMethod
from klira.sdk.exceptions import KliraConfigError
from klira.sdk.utils.validation import validate_endpoint

logger = logging.getLogger(__name__)


_DEFAULT_ENDPOINT = "https://api.getklira.com"
_DEFAULT_POLICIES_ENDPOINT = "https://api.getklira.com/v1/policies"


def _parse_bool(value: str | bool | None, default: bool = False) -> bool:
    if value is None:
        return default
    if isinstance(value, bool):
        return value
    return value.lower() in ("true", "1", "yes")


def _resolve(override: Any, env_key: str, default: Any = None) -> Any:
    """Resolve a config value: override takes precedence over env var.

    Unlike `override or env_var`, this correctly handles False/0/"" overrides.
    """
    if override is not None:
        return override
    env_val = os.environ.get(env_key)
    if env_val is not None:
        return env_val
    return default


def _resolve_anonymization(
    override: str | PhiMethod | bool | None, env_val: str | None
) -> PhiMethod | None:
    """Resolve the anonymization parameter into a PhiMethod or None.

    Accepts: "redact", "mask", "replace", "hash", PhiMethod enum, or None/False.
    """
    raw = override if override is not None else env_val
    if raw is None or raw is False:
        return None
    if isinstance(raw, PhiMethod):
        return raw
    if isinstance(raw, str):
        return PhiMethod(raw)
    return None


@dataclass(frozen=True)
class KliraConfig:
    """Immutable SDK configuration.

    Created once during Klira.init() and never mutated.
    To override, create a new instance. (Learning #15)
    """

    api_key: str
    app_name: str = "default"
    endpoint: str = _DEFAULT_ENDPOINT
    policies_endpoint: str = _DEFAULT_POLICIES_ENDPOINT

    # Tracing
    tracing_enabled: bool = True
    disable_external_tracing: bool = True

    # Guardrails
    use_remote_policies: bool = True
    policies_path: str | None = None
    llm_fallback_enabled: bool = False
    llm_fallback_provider: str | None = None
    llm_fallback_model: str | None = None
    llm_fallback_api_key: str | None = None
    llm_fallback_on_error: str = "allow"

    # PHI/PII — set to "redact", "mask", "replace", or "hash" to enable
    anonymization: PhiMethod | None = None
    phi_export_entity_details: bool = True

    # Framework
    framework: str | None = None

    # Healthcare
    clinical_domain: str | None = None

    # Evals
    evals_run: str | None = None
    dataset_id: str | None = None

    # Debug
    debug: bool = False

    def __post_init__(self) -> None:
        if not self.api_key.startswith("klira_"):
            raise KliraConfigError(
                "Invalid API key: must start with 'klira_'"
            )
        validate_endpoint(self.endpoint)
        validate_endpoint(self.policies_endpoint)

    def __repr__(self) -> str:
        def _mask(value: str | None) -> str:
            if value is None:
                return "None"
            if len(value) <= 8:
                return "'***'"
            return f"'{value[:6]}***'"

        fields = []
        for f in self.__dataclass_fields__:
            val = getattr(self, f)
            if f in ("api_key", "llm_fallback_api_key"):
                fields.append(f"{f}={_mask(val)}")
            else:
                fields.append(f"{f}={val!r}")
        return f"KliraConfig({', '.join(fields)})"

    @classmethod
    def from_env(cls, **overrides: Any) -> KliraConfig:
        """Create config from environment variables with optional overrides.

        Overrides take precedence over env vars. Uses _resolve() to
        correctly handle False/0/"" overrides (not treated as missing).
        """
        # Accept opentelemetry_endpoint as an alias for endpoint
        if "opentelemetry_endpoint" in overrides and "endpoint" not in overrides:
            overrides["endpoint"] = overrides.pop("opentelemetry_endpoint")
        else:
            overrides.pop("opentelemetry_endpoint", None)

        api_key = _resolve(overrides.pop("api_key", None), "KLIRA_API_KEY", "")
        if not api_key:
            raise KliraConfigError(
                "KLIRA_API_KEY must be set via environment variable or api_key parameter"
            )

        # Pop all known keys before constructing config so we can detect unknown ones
        kw_api_key = api_key
        kw_app_name = overrides.pop("app_name", None)
        kw_endpoint = overrides.pop("endpoint", None)
        kw_policies_endpoint = overrides.pop("policies_endpoint", None)
        kw_tracing_enabled = overrides.pop("tracing_enabled", None)
        kw_disable_external_tracing = overrides.pop("disable_external_tracing", None)
        kw_use_remote_policies = overrides.pop("use_remote_policies", None)
        kw_policies_path = overrides.pop("policies_path", None)
        kw_llm_fallback_enabled = overrides.pop("llm_fallback_enabled", None)
        kw_llm_fallback_provider = overrides.pop("llm_fallback_provider", None)
        kw_llm_fallback_model = overrides.pop("llm_fallback_model", None)
        kw_llm_fallback_api_key = overrides.pop("llm_fallback_api_key", None)
        kw_llm_fallback_on_error = overrides.pop("llm_fallback_on_error", None)
        kw_anonymization = overrides.pop("anonymization", None)
        kw_phi_export_entity_details = overrides.pop("phi_export_entity_details", None)
        kw_framework = overrides.pop("framework", None)
        kw_clinical_domain = overrides.pop("clinical_domain", None)
        kw_evals_run = overrides.pop("evals_run", None)
        kw_dataset_id = overrides.pop("dataset_id", None)
        kw_debug = overrides.pop("debug", None)

        if overrides:
            logger.warning(
                "Unknown Klira.init() parameters ignored: %s",
                ", ".join(sorted(overrides)),
            )

        return cls(
            api_key=kw_api_key,
            app_name=_resolve(kw_app_name, "KLIRA_APP_NAME", "default"),
            endpoint=_resolve(
                kw_endpoint,
                "KLIRA_OPENTELEMETRY_ENDPOINT",
                _resolve(None, "KLIRA_ENDPOINT", _DEFAULT_ENDPOINT),
            ),
            policies_endpoint=_resolve(
                kw_policies_endpoint,
                "KLIRA_POLICIES_ENDPOINT",
                _DEFAULT_POLICIES_ENDPOINT,
            ),
            tracing_enabled=_parse_bool(
                _resolve(kw_tracing_enabled, "KLIRA_TRACING_ENABLED"),
                default=True,
            ),
            disable_external_tracing=_parse_bool(
                _resolve(
                    kw_disable_external_tracing,
                    "KLIRA_DISABLE_EXTERNAL_TRACING",
                ),
                default=True,
            ),
            use_remote_policies=_parse_bool(
                _resolve(kw_use_remote_policies, "KLIRA_USE_REMOTE_POLICIES"),
                default=True,
            ),
            policies_path=_resolve(kw_policies_path, "KLIRA_POLICIES_PATH"),
            llm_fallback_enabled=_parse_bool(
                _resolve(kw_llm_fallback_enabled, "KLIRA_LLM_FALLBACK_ENABLED"),
                default=False,
            ),
            llm_fallback_provider=_resolve(
                kw_llm_fallback_provider, "KLIRA_LLM_FALLBACK_PROVIDER"
            ),
            llm_fallback_model=_resolve(
                kw_llm_fallback_model, "KLIRA_LLM_FALLBACK_MODEL"
            ),
            llm_fallback_api_key=_resolve(
                kw_llm_fallback_api_key, "KLIRA_LLM_FALLBACK_API_KEY"
            ),
            llm_fallback_on_error=_resolve(
                kw_llm_fallback_on_error, "KLIRA_LLM_FALLBACK_ON_ERROR", "allow"
            ),
            anonymization=_resolve_anonymization(
                kw_anonymization, os.environ.get("KLIRA_ANONYMIZATION")
            ),
            phi_export_entity_details=_parse_bool(
                _resolve(
                    kw_phi_export_entity_details,
                    "KLIRA_PHI_EXPORT_ENTITY_DETAILS",
                ),
                default=True,
            ),
            framework=_resolve(kw_framework, "KLIRA_FRAMEWORK"),
            clinical_domain=_resolve(kw_clinical_domain, "KLIRA_CLINICAL_DOMAIN"),
            evals_run=_resolve(kw_evals_run, "KLIRA_EVALS_RUN"),
            dataset_id=_resolve(kw_dataset_id, "KLIRA_DATASET_ID"),
            debug=_parse_bool(
                _resolve(kw_debug, "KLIRA_DEBUG"),
                default=False,
            ),
        )

    def is_eval_mode(self) -> bool:
        """Whether the SDK is in eval mode."""
        return self.evals_run is not None

    def get_telemetry_endpoint(self) -> str:
        """Get the telemetry endpoint (different in eval mode)."""
        if self.is_eval_mode():
            return f"{self.endpoint}/evals/v1/traces"
        return f"{self.endpoint}/v1/traces"
