"""Application settings.

Import-safe with no environment at all: the OpenAPI dump stage of the image build
imports `app.main` without a database or a BW client, so every setting has a
default that parses. Nothing here connects to anything.
"""

from functools import lru_cache

from pydantic_settings import BaseSettings, SettingsConfigDict

# A staged placeholder is a non-empty string, so `if not settings.x` would report
# a missing secret as configured. Test for this prefix instead (coding.md).
PLACEHOLDER_PREFIX = "REPLACE_WITH_"

# The session-secret fallback that lets the app import and run locally. Named here
# so the production guard in main.py cannot drift from the default below.
DEV_SESSION_SECRET = "dev-only-not-a-secret"


def _is_set(value: str | None) -> bool:
    """True only when a setting holds a real value, not an unfilled placeholder."""
    return bool(value) and not str(value).startswith(PLACEHOLDER_PREFIX)


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix="", extra="ignore")

    # --- database -------------------------------------------------------
    # Points at the compose sidecar over the project-internal Docker network.
    scout_db_url: str = "postgresql+psycopg://scout:scout@db:5432/scout"

    # --- app session ----------------------------------------------------
    # Signs the app's own session cookie. Entirely separate from any BW cookie.
    scout_session_secret: str = DEV_SESSION_SECRET

    # --- BW Auth (Pattern B) --------------------------------------------
    bw_client_id: str = ""
    bw_client_secret: str = ""
    bw_auth: str = "https://auth.bowden.works"
    bw_app_domain: str = "https://scout.bowden.works"

    # How the owner gated the host with `srv-gw id-gate`. A Pattern B app host is
    # never gated, so this is "public" and the app default-denies its own API.
    domain_mode: str = "public"

    # The app owner = the immutable super admin in the accounts kit. Not a level
    # row: the kit synthesizes them, and no mutation can demote or remove them.
    scout_owner: str = "rian"

    # Usernames seeded as admin on startup (comma-separated BW usernames). Seeded
    # only when an account's level is EMPTY — never overwrites a level set later.
    scout_initial_admins: str = ""

    # Refuses the dev fallbacks below when set to "production".
    app_env: str = "development"

    @property
    def is_production(self) -> bool:
        return self.app_env.lower() == "production"

    @property
    def has_bw_client(self) -> bool:
        """Whether BW Auth is really configured (placeholder-aware)."""
        return _is_set(self.bw_client_id) and _is_set(self.bw_client_secret)

    @property
    def initial_admins(self) -> list[str]:
        return [u.strip().lower() for u in self.scout_initial_admins.split(",") if u.strip()]


@lru_cache
def get_settings() -> Settings:
    return Settings()
