"""
RoleMapper: external groups/claims -> internal UserRole.

Configuration lives in the `settings` table under key `auth_role_mappings`
(JSON). Format:

    {
        "ldap": [
            {"pattern": "CN=VulnCheck-Admins,*", "role": "admin"},
            {"pattern": "CN=VulnCheck-Editors,*", "role": "editor"}
        ],
        "oidc": [
            {"pattern": "vulncheck-admins", "role": "admin"}
        ],
        "saml": [...]
    }

The first matching pattern wins (priority = list order). If no rule matches,
the orchestrator falls back to AUTH_JIT_DEFAULT_ROLE (readonly by default).
"""
from __future__ import annotations

import fnmatch
import json
import logging
from typing import Iterable, List, Optional

from sqlalchemy.orm import Session

from app.models.setting import Setting
from app.models.user import AuthProvider, UserRole

logger = logging.getLogger(__name__)

SETTINGS_KEY = "auth_role_mappings"


def _load_mappings(db: Session, provider: AuthProvider) -> List[dict]:
    s = db.query(Setting).filter(Setting.key == SETTINGS_KEY).first()
    if not s or not s.value:
        return []
    try:
        data = json.loads(s.value)
    except json.JSONDecodeError:
        logger.warning("auth_role_mappings is not valid JSON")
        return []
    rules = data.get(provider.value, [])
    if not isinstance(rules, list):
        return []
    return rules


def map_groups_to_role(
    db: Session,
    provider: AuthProvider,
    groups: Iterable[str],
    default: UserRole = UserRole.READONLY,
) -> tuple[UserRole, Optional[str]]:
    """
    Returns (mapped_role, matched_pattern_or_None).

    Patterns use fnmatch-style wildcards (`*`, `?`) and are matched
    case-insensitively against each raw group string.
    """
    groups_list = [g for g in groups if g]
    if not groups_list:
        return default, None

    rules = _load_mappings(db, provider)
    if not rules:
        return default, None

    groups_lower = [g.lower() for g in groups_list]
    for rule in rules:
        pattern = (rule.get("pattern") or "").lower()
        role_str = (rule.get("role") or "").lower()
        if not pattern or not role_str:
            continue
        for g in groups_lower:
            if fnmatch.fnmatch(g, pattern):
                try:
                    return UserRole(role_str), rule.get("pattern")
                except ValueError:
                    logger.warning("Invalid role '%s' in mapping rule", role_str)
                    continue

    return default, None
