"""Settings from the environment (.app.env / .bw-auth.env, mounted by compose).

Secrets are verified by SHAPE only (present / non-empty / not a placeholder) —
never echoed. Per /srv/projects/standards/coding.md: no secret appears in code,
config examples, chat, or logs.
"""

from __future__ import annotations

from functools import lru_cache

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

# A staged secret file uses REPLACE_WITH_* placeholders. Those are non-empty
# strings, so a plain truthiness check reports the feature as configured and the
# app fails on first real use instead of at startup. Test for the prefix
# (coding.md, "a staged placeholder must read as absent").
PLACEHOLDER_PREFIX = "REPLACE_WITH_"


def is_configured(value: str) -> bool:
    """True when a setting holds a real value — not empty, not a placeholder."""
    return bool(value) and not value.startswith(PLACEHOLDER_PREFIX)


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

    # --- core ---
    database_url: str = Field(default="", alias="DATABASE_URL")
    # This app's public origin, no trailing slash. bw_auth builds the BW
    # redirect_uri (<domain>/auth/callback) and the sign-out return from it.
    app_domain: str = Field(default="https://dailysplice.com", alias="BW_APP_DOMAIN")
    # BW Auth domain mode: public | password | account. dailysplice is PUBLIC —
    # a real public landing page plus an opt-in "Sign in" button.
    domain_mode: str = Field(default="public", alias="DOMAIN_MODE")

    # --- BW Auth (Pattern B, "Sign in with BW") ---
    # bw_auth.py reads BW_CLIENT_ID / BW_CLIENT_SECRET from os.environ directly
    # at call time. They are mirrored here only for shape reporting; the client
    # secret is never held in Python config and never logged.
    bw_client_id: str = Field(default="", alias="BW_CLIENT_ID")
    bw_auth: str = Field(default="https://auth.bowden.works", alias="BW_AUTH")

    # Secret that signs THIS app's own session cookie (username|exp|sig).
    # Entirely distinct from the BW client secret.
    app_session_secret: str = Field(default="", alias="APP_SESSION_SECRET")
    app_session_days: int = Field(default=30, alias="APP_SESSION_DAYS")

    # --- Spotify (a third-party integration, NOT an identity provider) ---
    # Users sign in with BW; Spotify is a per-user authorization they grant
    # afterwards. Credentials come from a Spotify app rian registers at
    # developer.spotify.com. Absent/placeholder = the feature reports itself
    # unconfigured and the UI hides the connect button.
    spotify_client_id: str = Field(default="", alias="SPOTIFY_CLIENT_ID")
    spotify_client_secret: str = Field(default="", alias="SPOTIFY_CLIENT_SECRET")

    # Fernet key encrypting third-party tokens at rest (services/crypto.py).
    # Distinct from every other secret here; losing it means users reconnect.
    token_encryption_key: str = Field(default="", alias="TOKEN_ENCRYPTION_KEY")

    @property
    def spotify_redirect_uri(self) -> str:
        """Derived, never configured separately — it MUST match the redirect URI
        registered in the Spotify dashboard byte for byte, and deriving it from
        one source removes the chance of the two drifting apart."""
        return f"{self.app_domain}/spotify/callback"

    @property
    def spotify_configured(self) -> bool:
        """Can a user connect Spotify on this deployment? Requires the client
        credentials AND working token encryption — without the latter the app
        must refuse to store tokens at all rather than store them in the clear."""
        return (
            is_configured(self.spotify_client_id)
            and is_configured(self.spotify_client_secret)
            and is_configured(self.token_encryption_key)
        )

    @property
    def auth_configured(self) -> bool:
        """Can this deployment complete a BW sign-in at all? Used to decide
        whether the SPA is told to offer sign-in, and to fail gated routes
        CLOSED (503) rather than open when the deployment is misconfigured."""
        return is_configured(self.bw_client_id) and is_configured(
            self.app_session_secret
        )

    def secret_status(self) -> dict[str, bool]:
        """Shape-only presence report for startup logging. Never values."""
        return {
            "database_url": is_configured(self.database_url),
            "app_session_secret": is_configured(self.app_session_secret),
            "bw_client_id": is_configured(self.bw_client_id),
            "spotify_client_id": is_configured(self.spotify_client_id),
            "spotify_client_secret": is_configured(self.spotify_client_secret),
            "token_encryption_key": is_configured(self.token_encryption_key),
        }


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