"""Settings for the alphaswarm_admin backend.

Reads ``ALPHASWARM_ADMIN_*`` environment variables and falls back to safe local
defaults. Per AlphaSwarm rule 7 the project never instantiates a fresh settings
object directly; consumers go through :func:`get_settings`.

Auth posture: Entra-only. Post the alphaswarm_admin Entra refactor
(``.cursor/plans/alphaswarm_admin_entra_refactor_039f2aeb.plan.md``)
this BFF validates every inbound bearer against the AlphaSwarm staff
Microsoft Entra tenant and mints outbound M2M tokens via per-deployment
Entra Agent Identities (Blueprint -> BlueprintPrincipal -> Agent
Identity, per the entra-agent-id skill). Auth0 has been REMOVED from
this surface — the legacy Vite SPA's Auth0 path remains under the
30-day rollback feature flag in ``alphaswarm_admin_ui/``.
"""
from __future__ import annotations

from functools import lru_cache
from typing import Literal
from urllib.parse import urlparse

from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


def _is_loopback_url(url: str) -> bool:
    """Return True when the URL host resolves to localhost/loopback."""
    try:
        parsed = urlparse(url)
    except ValueError:
        return False
    host = (parsed.hostname or "").lower()
    return host in {"localhost", "127.0.0.1", "::1"}


class AdminSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="",  # mix ALPHASWARM_ADMIN_*, ALPHASWARM_AUTH_*, ALPHASWARM_CP_* without per-class prefix
        env_file=None,
        extra="ignore",
        case_sensitive=False,
    )

    # --- Service topology ----------------------------------------------
    api_url: str = Field(
        default="http://localhost:8000",
        alias="ALPHASWARM_ADMIN_API_URL",
        description="AlphaSwarm monolith base URL.",
    )
    control_plane_url: str = Field(
        default="http://localhost:9000",
        alias="ALPHASWARM_ADMIN_CONTROL_PLANE_URL",
        description="alphaswarm_controller base URL.",
    )
    cors_origins: list[str] = Field(
        default_factory=lambda: ["http://localhost:3003"],
        alias="ALPHASWARM_ADMIN_CORS_ORIGINS",
        description="Allowed CORS origins for the admin SPA.",
    )

    # --- Auth (Entra-only) ---------------------------------------------
    # Post the alphaswarm_admin Entra refactor: Auth0 is removed from this
    # surface. The ``auth_provider`` field is kept as a frozen
    # ``Literal["msal_entra"]`` so any deployment still passing
    # ``ALPHASWARM_ADMIN_AUTH_PROVIDER=auth0`` fails fast at boot with a
    # clear validation error rather than silently degrading.
    auth_required: bool = Field(
        default=True,
        alias="ALPHASWARM_ADMIN_AUTH_REQUIRED",
        description="Set False only for local sandboxes; never in production.",
    )
    auth_provider: Literal["msal_entra"] = Field(
        default="msal_entra",
        alias="ALPHASWARM_ADMIN_AUTH_PROVIDER",
        description=(
            "Frozen to ``msal_entra``. Auth0 was removed from "
            "alphaswarm_admin in the Entra refactor; the legacy Vite SPA "
            "rollback path lives in alphaswarm_admin_ui/."
        ),
    )
    auth_entra_tenant: str = Field(
        default="organizations",
        alias="ALPHASWARM_ADMIN_ENTRA_TENANT",
        description=(
            "Entra tenant segment used to derive issuer + JWKS URLs. "
            "Use a tenant UUID for single-tenant, 'organizations' for "
            "any Entra tenant (B2B), 'common' to additionally accept "
            "personal Microsoft accounts."
        ),
    )
    auth_oidc_audience: str = Field(
        default="api://alphaswarm-manage-api",
        alias="ALPHASWARM_AUTH_OIDC_AUDIENCE",
        description=(
            "API resource identifier configured on the IdP. Defaults to "
            "the canonical ``api://alphaswarm-manage-api`` audience minted by "
            "the alphaswarm_entra_directory Terraform module. Override only "
            "when running the admin behind a separate Resource Server."
        ),
    )
    auth_claims_namespace: str = Field(
        default="https://alphaswarm.internal/",
        alias="ALPHASWARM_AUTH_CLAIMS_NAMESPACE",
    )
    auth_claims_namespace_aliases: list[str] = Field(
        default_factory=lambda: ["https://alphaswarm/"],
        alias="ALPHASWARM_AUTH_CLAIMS_NAMESPACE_ALIASES",
    )
    auth_jwks_ttl_seconds: int = Field(
        default=600,
        alias="ALPHASWARM_ADMIN_AUTH_JWKS_TTL_SECONDS",
        ge=60,
    )
    auth_leeway_seconds: int = Field(
        default=60,
        alias="ALPHASWARM_ADMIN_AUTH_LEEWAY_SECONDS",
        ge=0,
        le=300,
    )

    # --- AlphaSwarm staff Entra tenant (Workstream "Entra internal tenant") ---
    # When the env vars below are set, the admin BFF pins token validation
    # to the AlphaSwarm staff Entra tenant id (single-tenant) instead of the
    # multi-tenant ``organizations`` authority. Mirrors the monolith
    # settings landed by the Entra-internal-tenant rollout. See
    # docs/plans/entra-internal-tenant-rollout.md.
    auth_msal_internal_tenant_id: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_MSAL_INTERNAL_TENANT_ID",
        description=(
            "AlphaSwarm staff Entra tenant id (UUID). When set, the admin "
            "validator pins the issuer to "
            "``https://login.microsoftonline.com/{tenant_id}/v2.0`` and "
            "ignores ``auth_entra_tenant``."
        ),
    )
    auth_msal_staff_app_id: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_MSAL_INTERNAL_APP_ID",
        description=(
            "Application (client) id of the alphaswarm-staff Terraform-managed "
            "app registration. Plumbed into the frontend MSAL "
            "PublicClientApplication so the admin SPA can mint tokens."
        ),
    )
    auth_msal_internal_audience: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_MSAL_INTERNAL_AUDIENCE",
        description=(
            "Optional override for the manage-API audience. Empty falls "
            "back to ``auth_oidc_audience`` (default ``api://alphaswarm-manage-api``)."
        ),
    )
    auth_msal_redirect_path: str = Field(
        default="/api/auth/entra/callback",
        alias="ALPHASWARM_ADMIN_ENTRA_REDIRECT_PATH",
        description=(
            "Path on the admin SPA the MSAL redirect lands on. Combined "
            "with the deployed origin to form the full redirect URI; "
            "MUST match a redirect URI registered on the staff app."
        ),
    )

    # --- Agent Identity (entra-agent-id skill, M2M outbound) -----------
    # Per-deployment Entra Agent Identity values plumbed from the
    # alphaswarm_admin_agent_identity Terraform module's outputs (see
    # alphaswarm_platform/terraform/modules/alphaswarm_admin_agent_identity/).
    # The Blueprint id is shared across deployments; the Agent Identity
    # id + fmi_path are unique per deployment so each environment's
    # tokens carry a distinct ``sub`` claim for audit / RBAC routing.
    auth_agent_identity_enabled: bool = Field(
        default=False,
        alias="ALPHASWARM_AUTH_AGENT_IDENTITY_ENABLED",
        description=(
            "When True, outbound M2M tokens to alphaswarm_controller + "
            "the AlphaSwarm monolith are minted via the two-step fmi_path "
            "exchange instead of the legacy client_credentials path. "
            "Requires ``auth_agent_blueprint_app_id`` and "
            "``auth_agent_identity_id`` to be populated."
        ),
    )
    auth_agent_blueprint_app_id: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_AGENT_BLUEPRINT_APP_ID",
        description=(
            "Application (client) id of the alphaswarm-admin-service "
            "Entra Agent Identity Blueprint. Sourced from "
            "``module.alphaswarm_admin_agent_identity.blueprint_app_id`` "
            "in the wiley-tech Terraform environment."
        ),
    )
    auth_agent_identity_id: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_AGENT_IDENTITY_ID",
        description=(
            "Object id of the per-deployment Entra Agent Identity. "
            "One per environment (dev / staging / prod). Sourced from "
            "``module.alphaswarm_admin_agent_identity.agent_identity_ids[<env>]``."
        ),
    )
    auth_agent_identity_fmi_path: str = Field(
        default="alphaswarm-admin-default",
        alias="ALPHASWARM_AUTH_AGENT_FMI_PATH",
        description=(
            "fmi_path used in the Step 2 exchange. Acts as an audit label "
            "in the Entra token endpoint logs; conventionally "
            "``alphaswarm-admin-<env>``."
        ),
    )
    auth_agent_fic_file: str = Field(
        default="",
        alias="ALPHASWARM_AUTH_AGENT_FIC_FILE",
        description=(
            "Optional path to the federated identity credential token "
            "file the Blueprint authenticates with at Step 1 (no "
            "client_secret). When empty the resolver falls back to "
            "``AZURE_FEDERATED_TOKEN_FILE`` (canonical Azure WIF mount "
            "at ``/var/run/secrets/azure/tokens/azure-identity-token``)."
        ),
    )
    auth_entra_client_secret_file: str = Field(
        default=r"C:\Users\Julian Wiley\Documents\adminapi.txt",
        alias="ALPHASWARM_AUTH_ENTRA_CLIENT_SECRET_FILE",
        description=(
            "Path to a file containing the admin app-registration's "
            "Entra client_secret. Used as the canonical bootstrap "
            "source for outbound M2M when Entra Agent Identity is not "
            "yet configured. File format is the two-line "
            "``value: <secret>\\nid: <secret-id>`` shape produced by "
            "``az ad app credential add`` and the Azure portal "
            "'Certificates & secrets' pane. Read by "
            "``FileBasedAdminSecretStore`` at ``PRIORITY_FILE=50`` "
            "(ahead of env=100, behind Agent Identity=4). Per AGENTS "
            "rule 7 the file contents are never logged or returned."
        ),
    )

    # --- Cloud account onboarding (federated-first) --------------------
    # Used by the per-cloud AccountIntegrationProvider implementations in
    # alphaswarm_admin.providers.cloud_* to (a) embed the AlphaSwarm-side principal in
    # the trust policies / federated credentials the wizard emits in
    # step 2 "Bootstrap artifacts", and (b) derive a stable per-org
    # external_id that customers cannot enumerate. NONE of these values
    # are secret on their own; the HMAC key (the only secret) is loaded
    # through the CredentialResolver chain in the provider — set
    # ``ALPHASWARM_ADMIN_CLOUD_AWS_EXTERNAL_ID_SECRET`` only as a dev fallback.
    cloud_aws_partner_account_id: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_AWS_PARTNER_ACCOUNT_ID",
        description=(
            "AlphaSwarm's AWS account id (12 digits). Embedded as the "
            "``Principal.AWS`` of the trust policy the AWS onboarding "
            "wizard generates for the customer. Required before the "
            "AWS wizard can emit a bootstrap template."
        ),
    )
    cloud_aws_external_id_secret: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_CLOUD_AWS_EXTERNAL_ID_SECRET",
        description=(
            "HMAC key used to derive the per-org ``sts:ExternalId`` "
            "value. When empty, the provider uses a process-ephemeral "
            "key so dev sandboxes keep working; production deployments "
            "MUST set this so the external id is stable across "
            "rebuilds. Resolved through the CredentialResolver chain "
            "when wired."
        ),
    )
    cloud_aws_role_name_pattern: str = Field(
        default="alphaswarm-broker-{org_id}",
        alias="ALPHASWARM_ADMIN_AWS_ROLE_NAME_PATTERN",
        description=(
            "Default IAM role name the wizard suggests on the customer "
            "side. The ``{org_id}`` placeholder is interpolated at "
            "template-emit time so the customer can copy/paste."
        ),
    )
    # --- AWS Identity Center / SSO (operator-attributed outbound auth) ---
    # Plumbs into the ``sso_permission_set`` auth-method branch of the
    # ``cloud_aws`` provider. Operators sign in via the OIDC device flow
    # (``sso-oidc.start_device_authorization``), pick a (account, role)
    # pair from ``sso.list_accounts`` / ``list_account_roles``, and the
    # resulting STS creds are cached in :class:`AwsSsoCredentialStore`.
    # Per AGENTS rule 7 these are NOT secret values; the SSO access
    # token + STS creds resolve through :class:`CredentialResolver`.
    cloud_aws_sso_start_url: str = Field(
        default="",
        alias="ALPHASWARM_AWS_SSO_START_URL",
        description=(
            "AWS Identity Center start URL "
            "(e.g. https://alphaswarm.awsapps.com/start). The operator "
            "is redirected here after the OIDC device flow. When empty "
            "the SSO branch is hidden from the wizard."
        ),
    )
    cloud_aws_sso_region: str = Field(
        default="us-east-1",
        alias="ALPHASWARM_AWS_SSO_REGION",
        description=(
            "AWS region the Identity Center instance lives in. "
            "Required by the boto3 ``sso-oidc`` and ``sso`` clients; "
            "this is the home region of the Identity Center instance, "
            "NOT the workload region."
        ),
    )
    cloud_aws_identity_center_instance_arn: str = Field(
        default="",
        alias="ALPHASWARM_AWS_IDENTITY_CENTER_INSTANCE_ARN",
        description=(
            "Full ARN of the Identity Center instance "
            "(e.g. arn:aws:sso:::instance/ssoins-XXXXXXXXXXXXXXXX). "
            "Used by the Phase 2 Terraform module to pin permission "
            "set + account assignment provisioning."
        ),
    )
    cloud_aws_sso_default_permission_set: str = Field(
        default="",
        alias="ALPHASWARM_AWS_SSO_DEFAULT_PERMISSION_SET",
        description=(
            "Per-environment hint for the wizard's default permission "
            "set picker (e.g. AlphaSwarmPlatformOperator). Operators "
            "still pick at login; this is just the pre-selected value."
        ),
    )
    cloud_azure_app_client_id: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_AZURE_APP_CLIENT_ID",
        description=(
            "Application (client) id of the AlphaSwarm Entra app that will "
            "carry the federated credential. The Azure onboarding "
            "wizard surfaces this value in step 2 so the customer "
            "knows which app the federation links to."
        ),
    )
    cloud_azure_app_object_id: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_AZURE_APP_OBJECT_ID",
        description=(
            "Entra app object id (parent for ``az ad app "
            "federated-credential create``). Required before the "
            "Azure wizard can emit a federated-credential JSON skeleton."
        ),
    )
    cloud_azure_audience: str = Field(
        default="api://AzureADTokenExchange",
        alias="ALPHASWARM_ADMIN_AZURE_AUDIENCE",
        description=(
            "Audience the customer's federated identity provider must "
            "include in the issued token. ``api://AzureADTokenExchange`` "
            "is the canonical Entra value for WIF."
        ),
    )
    cloud_gcp_wif_audience: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_GCP_WIF_AUDIENCE",
        description=(
            "Audience template injected into the GCP Workload Identity "
            "Pool provider config. Customer-side ``gcloud iam "
            "workload-identity-pools providers create-oidc`` will use "
            "this value as ``--allowed-audiences``."
        ),
    )
    cloud_gcp_wif_service_account_email: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_GCP_WIF_SERVICE_ACCOUNT_EMAIL",
        description=(
            "AlphaSwarm-side service account the customer's WIF principal "
            "impersonates. The wizard suggests ``iam.workloadIdentityUser`` "
            "be granted on this SA so impersonation succeeds."
        ),
    )
    cloud_cloudflare_token_template: str = Field(
        default="dns_edit",
        alias="ALPHASWARM_ADMIN_CLOUDFLARE_TOKEN_TEMPLATE",
        description=(
            "Default scoped-API-token template the Cloudflare wizard "
            "suggests when the operator does not specify one. Valid "
            "values: dns_edit, tunnel, access, worker, r2."
        ),
    )

    # --- Platform deployment control (AWS ECS) -------------------------
    # Consumed by alphaswarm_admin.services.platform_deployment to drive
    # the hosted platform's OWN ECS Fargate slice (admin + agentcore
    # proxy) via boto3 — the counterpart to the broker-delegated
    # /admin/deployments surface (which governs customer workloads).
    # On ECS Fargate the admin authenticates with its task role (granted
    # by the ecs-fargate-control-plane Terraform module); the optional
    # assume-role fields let a locally-run admin or a cross-account
    # control plane target a remote cluster. None of these are secret.
    platform_aws_region: str = Field(
        default="us-east-1",
        alias="ALPHASWARM_ADMIN_PLATFORM_AWS_REGION",
        description="AWS region the platform's ECS control-plane cluster runs in.",
    )
    platform_ecs_cluster: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_PLATFORM_ECS_CLUSTER",
        description=(
            "ECS cluster name (or ARN) hosting the platform control-plane "
            "services. Required before the /admin/platform/ecs surface can "
            "act; sourced from the ecs-fargate-control-plane module's "
            "``ecs_cluster_name`` SSM parameter."
        ),
    )
    platform_aws_assume_role_arn: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_PLATFORM_AWS_ASSUME_ROLE_ARN",
        description=(
            "Optional IAM role ARN the admin assumes before calling ECS / "
            "CloudWatch. Empty uses the ambient credential chain (the ECS "
            "task role on Fargate)."
        ),
    )
    platform_aws_external_id: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_PLATFORM_AWS_EXTERNAL_ID",
        description=(
            "Optional ``sts:ExternalId`` paired with "
            "``platform_aws_assume_role_arn`` for cross-account control."
        ),
    )
    platform_alarm_prefix: str = Field(
        default="alphaswarm-",
        alias="ALPHASWARM_ADMIN_PLATFORM_ALARM_PREFIX",
        description=(
            "CloudWatch alarm-name prefix used to scope the alarm listing "
            "to the platform's alarms. Empty lists all alarms."
        ),
    )

    # --- M2M broker (admin -> control plane) ---------------------------
    m2m_credential_service: str = Field(
        default="alphaswarm-admin-to-cp",
        alias="ALPHASWARM_ADMIN_M2M_CREDENTIAL_SERVICE",
        description=(
            "CredentialResolver service name for the admin BFF's "
            "control-plane client credentials."
        ),
    )
    m2m_credential_purpose: str = Field(
        default="client_credentials",
        alias="ALPHASWARM_ADMIN_M2M_CREDENTIAL_PURPOSE",
    )
    m2m_cp_audience: str = Field(
        default="api://alphaswarm-controller",
        alias="ALPHASWARM_ADMIN_M2M_CP_AUDIENCE",
        description="Audience claim the control plane validates on incoming M2M tokens.",
    )
    # --- Phase 1: Auth-through-controller (launcher / auth-provider /
    # connection-manager refactor). When True, the admin BFF's
    # ``M2MTokenBroker`` swaps its ``MsalEntraValidator`` provider for
    # the new ``ControllerIdentityProviderShim`` so every outbound M2M
    # call flows through ``alphaswarm_controller`` /auth/m2m/token
    # rather than hitting Entra directly. Defaults to False so the
    # existing behaviour stays intact during the rollout.
    auth_through_controller: bool = Field(
        default=False,
        alias="ALPHASWARM_AUTH_THROUGH_CONTROLLER",
        description=(
            "Master flag for routing admin M2M issuance through the "
            "controller's /auth/m2m/token surface. When False, admin "
            "talks to Entra directly via MsalEntraValidator."
        ),
    )

    # --- Audit ---------------------------------------------------------
    audit_sink: str = Field(
        default="jsonl",
        alias="ALPHASWARM_ADMIN_AUDIT_SINK",
        description="Audit sink: 'jsonl' for local file or 'http' to post to monolith.",
    )
    audit_jsonl_path: str = Field(
        default="./admin_audit.jsonl",
        alias="ALPHASWARM_ADMIN_AUDIT_JSONL_PATH",
        description="JSONL fallback path when audit_sink == 'jsonl'.",
    )
    audit_http_url: str = Field(
        default="",
        alias="ALPHASWARM_ADMIN_AUDIT_HTTP_URL",
        description=(
            "Monolith audit ingest URL (e.g. "
            "http://localhost:8000/_internal/audit/admin-runs). "
            "Required when audit_sink == 'http'."
        ),
    )

    @model_validator(mode="after")
    def _derive_local_auth_default(self) -> "AdminSettings":
        """Disable auth by default only for localhost-only topologies.

        Production/staging deployments keep auth enabled unless explicitly
        configured otherwise; local sandbox contributors can run the admin
        surface without first wiring an IdP app registration.
        """
        if "auth_required" in self.model_fields_set:
            return self
        if _is_loopback_url(self.api_url) and _is_loopback_url(self.control_plane_url):
            self.auth_required = False
        return self

    @property
    def auth_enabled(self) -> bool:
        """Auth requires both ``auth_required=true`` and a resolvable issuer."""
        if not self.auth_required:
            return False
        if self.auth_oidc_issuer:
            return True
        # Entra-primary path derives the issuer from the tenant segment.
        return bool(self.auth_entra_tenant)

    def load_entra_client_secret_from_file(self) -> tuple[str, str] | None:
        """Read ``auth_entra_client_secret_file`` and parse out ``(value, id)``.

        Returns ``None`` when the path is empty, the file does not
        exist, cannot be opened, or does not contain a ``value:`` line.
        The ``id:`` line is optional; the second tuple element is the
        empty string when absent.

        Per AGENTS rule 7 the parsed secret is NEVER logged. Callers
        must keep the returned value in local scope (e.g. inject it
        straight into a ``Credential`` fields dict). Consumed by
        :class:`FileBasedAdminSecretStore` in
        :mod:`alphaswarm_admin.integrations.broker`.
        """
        path = (self.auth_entra_client_secret_file or "").strip()
        if not path:
            return None
        try:
            with open(path, encoding="utf-8") as handle:
                content = handle.read()
        except OSError:
            return None
        secret_value = ""
        secret_id = ""
        for raw_line in content.splitlines():
            line = raw_line.strip()
            if not line or line.startswith("#"):
                continue
            key, sep, val = line.partition(":")
            if not sep:
                continue
            key_norm = key.strip().lower()
            val_norm = val.strip()
            if key_norm == "value":
                secret_value = val_norm
            elif key_norm == "id":
                secret_id = val_norm
        if not secret_value:
            return None
        return secret_value, secret_id


@lru_cache(maxsize=1)
def get_settings() -> AdminSettings:
    return AdminSettings()  # type: ignore[call-arg]


def reset_settings_cache() -> None:
    get_settings.cache_clear()
