"""Tests for the consolidated quber.settings package (QUE-228).

The autouse fixture chdirs into an empty temp dir so the suite never reads
the developer's real ``.env``; env presence is simulated with
``monkeypatch.setenv`` (no import-time env gates, per project conventions).
"""

from pathlib import Path

import pytest

from quber.settings import (
    DBSettings,
    EmbeddingSettings,
    LLMSettings,
    ObservabilitySettings,
    S3CacheSettings,
    Settings,
    get_settings,
)

# Env vars touched across the suite; cleared before each test so defaults are
# deterministic regardless of the host environment.
MANAGED_VARS = [
    "QUBER_LLM_MODEL",
    "ANTHROPIC_MODEL",
    "ANTHROPIC_API_KEY",
    "ANTHROPIC_AUTH_TOKEN",
    "OPENAI_API_KEY",
    "QUBER_LLM_BACKEND",
    "QUBER_UNIFIER_BACKEND",
    "QUBER_CLASSIFIER_BACKEND",
    "QUBER_DETECTOR_BACKEND",
    "QUBER_GRID_LOCATOR_BACKEND",
    "QUBER_COMPLETENESS_BACKEND",
    "POSTGRES_USER",
    "POSTGRES_PASSWORD",
    "POSTGRES_HOST",
    "POSTGRES_PORT",
    "POSTGRES_DB",
    "QUBER_S3_CACHE_DIR",
    "QUBER_S3_CACHE_TTL",
    "QUBER_S3_REGION",
    "AWS_REGION",
    "EMBEDDING_PROVIDER",
    "EMBEDDING_DEVICE",
    "PYDANTIC_LOGFIRE_TOKEN",
    "LOGFIRE_TOKEN",
    "QUBER_ENABLE_LOGFIRE",
    "TRACE_TO_LANGSMITH",
    "CC_LANGSMITH_API_KEY",
    "CC_LANGSMITH_PROJECT",
    "CC_LANGSMITH_DEBUG",
    "QUBER_LOG_LEVEL",
    "QUBER_MAX_CONCURRENT_TABLES",
]


@pytest.fixture(autouse=True)
def isolated_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path):
    # Clear managed vars and chdir into an empty dir so no real `.env` is read;
    # settings then resolve purely from the (cleared) environment.
    for var in MANAGED_VARS:
        monkeypatch.delenv(var, raising=False)
    monkeypatch.chdir(tmp_path)
    get_settings.cache_clear()
    yield
    get_settings.cache_clear()


# --- Defaults (must match the pre-migration values) --------------------------


def test_llm_defaults():
    s = LLMSettings()
    assert s.model is None
    assert s.anthropic_api_key is None
    assert s.anthropic_auth_token is None
    assert s.openai_api_key is None
    assert s.llm_backend == "api"
    assert s.unifier_backend == "api"
    assert s.classifier_backend == "api"
    assert s.detector_backend == "api"
    assert s.grid_locator_backend == "api"
    # completeness historically defaults to "text", not "api"
    assert s.completeness_backend == "text"


def test_db_defaults_match_legacy():
    s = DBSettings()
    assert (s.user, s.password, s.host, s.port, s.db) == (
        "quber",
        "quber_dev",
        "localhost",
        5432,
        "quber_rag",
    )


def test_s3_cache_defaults_match_legacy():
    s = S3CacheSettings()
    assert s.cache_dir == Path(".cache/s3")
    assert s.ttl_seconds == 5 * 86400
    assert s.region is None


def test_embedding_defaults():
    s = EmbeddingSettings()
    assert s.provider == "local"
    assert s.device == "cuda"


def test_observability_defaults():
    s = ObservabilitySettings()
    assert s.logfire_token is None
    assert s.enable_logfire is True
    assert s.trace_to_langsmith is False
    assert s.langsmith_api_key is None
    assert s.langsmith_project == "default"
    assert s.langsmith_debug is False


def test_root_defaults():
    s = Settings()
    assert s.log_level == "INFO"
    assert s.max_concurrent_tables == 5


# --- Flat legacy env names populate fields -----------------------------------


def test_flat_env_names_populate_llm(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("QUBER_LLM_MODEL", "claude-haiku-4-5-20251001")
    monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-flat")
    monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "oauth-flat")
    monkeypatch.setenv("QUBER_UNIFIER_BACKEND", "mock")
    s = LLMSettings()
    assert s.model == "claude-haiku-4-5-20251001"
    assert s.anthropic_api_key == "sk-flat"
    assert s.anthropic_auth_token == "oauth-flat"
    assert s.unifier_backend == "mock"


def test_flat_env_names_populate_db(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("POSTGRES_USER", "u")
    monkeypatch.setenv("POSTGRES_PORT", "6543")
    s = DBSettings()
    assert s.user == "u"
    assert s.port == 6543


# --- AliasChoices transitional fallbacks -------------------------------------


def test_logfire_legacy_alias(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("LOGFIRE_TOKEN", "lf-legacy")
    assert ObservabilitySettings().logfire_token == "lf-legacy"


def test_logfire_canonical_wins_over_legacy(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("LOGFIRE_TOKEN", "lf-legacy")
    monkeypatch.setenv("PYDANTIC_LOGFIRE_TOKEN", "lf-canonical")
    assert ObservabilitySettings().logfire_token == "lf-canonical"


def test_anthropic_model_legacy_alias_for_llm_model(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("ANTHROPIC_MODEL", "claude-legacy")
    assert LLMSettings().model == "claude-legacy"


def test_aws_region_alias_for_s3_region(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("AWS_REGION", "us-east-1")
    assert S3CacheSettings().region == "us-east-1"


# --- bool parsing parity with the old truthy() helper ------------------------


@pytest.mark.parametrize("token", ["1", "true", "yes", "on", "TRUE", "On", "Yes"])
def test_trace_to_langsmith_truthy_tokens(monkeypatch: pytest.MonkeyPatch, token: str):
    monkeypatch.setenv("TRACE_TO_LANGSMITH", token)
    assert ObservabilitySettings().trace_to_langsmith is True


@pytest.mark.parametrize("token", ["0", "false", "no", "off", ""])
def test_trace_to_langsmith_falsey_tokens(monkeypatch: pytest.MonkeyPatch, token: str):
    monkeypatch.setenv("TRACE_TO_LANGSMITH", token)
    assert ObservabilitySettings().trace_to_langsmith is False


# --- Root composition + cached singleton -------------------------------------


def test_root_nests_all_groups(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-root")
    monkeypatch.setenv("POSTGRES_DB", "rag2")
    s = Settings()
    assert s.llm.anthropic_api_key == "sk-root"
    assert s.db.db == "rag2"
    assert isinstance(s.obs, ObservabilitySettings)


def test_get_settings_is_cached():
    assert get_settings() is get_settings()
