"""
SAML 2.0 authentication strategy.

Uses python3-saml (OneLogin), which delegates XML signature validation to
xmlsec1. python3-saml has built-in protection against:
- XML Signature Wrapping (XSW): strict assertion/response signature
  position validation
- Replay (per-NotOnOrAfter window)
- Audience mismatch

This strategy does NOT do credential auth — it's a callback-only strategy.
The router (app/routers/auth_saml.py) drives the SP-initiated and
IdP-initiated flows.
"""
from __future__ import annotations

import logging
import os
from typing import List

from app.auth.strategies.base import AuthError, AuthStrategy, ExternalIdentity
from app.models.user import AuthProvider

logger = logging.getLogger(__name__)


def _split_groups(value) -> List[str]:
    if value is None:
        return []
    if isinstance(value, list):
        return [str(v) for v in value if v]
    if isinstance(value, str):
        return [g.strip() for g in value.split(",") if g.strip()]
    return [str(value)]


class SamlStrategy(AuthStrategy):
    """
    SAML 2.0 strategy. Most config lives in the SP/IdP YAML used by
    python3-saml directly; here we just hold attribute-name mappings.
    """

    name = AuthProvider.SAML

    def __init__(self):
        self.attr_username = os.getenv(
            "SAML_ATTR_USERNAME", "urn:oid:0.9.2342.19200300.100.1.1"
        )
        self.attr_email = os.getenv("SAML_ATTR_EMAIL", "urn:oid:1.2.840.113549.1.9.1")
        self.attr_groups = os.getenv("SAML_ATTR_GROUPS", "http://schemas.xmlsoap.org/claims/Group")
        self.attr_display = os.getenv("SAML_ATTR_DISPLAY", "urn:oid:2.16.840.1.113730.3.1.241")

        self.idp_metadata_url = os.getenv("SAML_IDP_METADATA_URL", "").strip()
        self.idp_metadata_path = os.getenv("SAML_IDP_METADATA_PATH", "").strip()

    @property
    def is_enabled(self) -> bool:
        # SAML is enabled if either metadata source is configured AND
        # SP cert + key paths exist.
        sp_cert = os.getenv("SAML_SP_CERT_PATH", "")
        sp_key = os.getenv("SAML_SP_PRIVATE_KEY_PATH", "")
        return bool(
            (self.idp_metadata_url or self.idp_metadata_path)
            and sp_cert
            and sp_key
        )

    def authenticate_credentials(self, username: str, password: str) -> ExternalIdentity:
        raise NotImplementedError("SAML is redirect-based; use the /auth/saml/* router.")

    def parse_attributes(self, attributes: dict, name_id: str) -> ExternalIdentity:
        """
        Called by the router after python3-saml has validated the response
        (signature, audience, replay) — we just map attributes → identity.
        """
        def _first(attr_name: str, fallback: str = "") -> str:
            val = attributes.get(attr_name)
            if isinstance(val, list):
                return str(val[0]) if val else fallback
            return str(val) if val else fallback

        username = _first(self.attr_username, fallback=name_id)
        email = _first(self.attr_email, fallback=name_id if "@" in name_id else f"{name_id}@saml.local")
        display = _first(self.attr_display, fallback=username)
        groups = _split_groups(attributes.get(self.attr_groups))

        return ExternalIdentity(
            provider=AuthProvider.SAML,
            external_id=name_id,  # IdP-issued, stable
            username=username,
            email=email,
            display_name=display,
            groups=groups,
        )
