"""
Role + scope helpers for FastAPI.

Design: additive / non-breaking rollout
- Master admin, business admin, admin, and user roles keep full business access (current behavior).
- Manager/agent scopes apply ONLY when explicit mappings exist in user_group_mapping /
  user_agent_mapping; otherwise those roles also see full business data (migration-safe).
- Set RBAC_STRICT=1 to always enforce manager/agent scopes (even with empty mappings → no rows).
"""

from __future__ import annotations

import os
from typing import Any, Dict, List, Optional, Tuple

from auth_handler import AuthHandler

ADMIN_ROLES = frozenset({"admin", "business_admin"})
FULL_ACCESS_ROLES = frozenset({"admin", "business_admin", "user"})
SCOPED_ROLES = frozenset({"manager", "agent"})


def rbac_strict() -> bool:
    return os.getenv("RBAC_STRICT", "").strip().lower() in ("1", "true", "yes", "on")


def normalize_role(role: Optional[str]) -> str:
    return AuthHandler.normalize_role(role)


def get_business_entry(user: Dict[str, Any], bid: str) -> Optional[Dict[str, Any]]:
    if not user:
        return None
    if user.get("is_master"):
        return {"bid": str(bid), "role": "admin", "permissions": ["*"], "scope": {"type": "all"}}
    for business in user.get("businesses") or []:
        if str(business.get("bid")) == str(bid):
            return business
    return None


def resolve_business_context(
    user: Dict[str, Any],
    bid: str,
    auth_handler: AuthHandler,
    *,
    reload: bool = False,
) -> Dict[str, Any]:
    """Return role, permissions, scope for one business (reload from DB when requested)."""
    if user.get("is_master"):
        return {
            "bid": str(bid),
            "role": "admin",
            "permissions": ["*"],
            "scope": {"type": "all"},
        }

    entry = get_business_entry(user, bid)
    if not entry:
        return {"bid": str(bid), "role": "user", "permissions": [], "scope": {"type": "none"}}

    user_id = user.get("id")
    if reload and user_id:
        businesses = auth_handler.get_user_businesses(user_id) or []
        db_entry = next((b for b in businesses if str(b.get("bid")) == str(bid)), None)
        if db_entry:
            role = normalize_role(db_entry.get("role"))
            permissions = list(db_entry.get("permissions") or auth_handler.get_permissions_for_user(user_id, bid, role))
            scope = dict(db_entry.get("scope") or auth_handler.get_user_scope(user_id, bid, role))
            return {"bid": str(bid), "role": role, "permissions": permissions, "scope": scope}

    role = normalize_role(entry.get("role"))
    permissions = list(entry.get("permissions") or [])
    scope = dict(entry.get("scope") or {"type": "business"})

    return {"bid": str(bid), "role": role, "permissions": permissions, "scope": scope}


def has_permission(
    user: Dict[str, Any],
    bid: str,
    permission: str,
    auth_handler: AuthHandler,
    *,
    reload: bool = False,
) -> bool:
    if user.get("is_master"):
        return True
    ctx = resolve_business_context(user, bid, auth_handler, reload=reload)
    perms = ctx.get("permissions") or []
    if "*" in perms:
        return True
    return str(permission) in perms


def is_business_admin(user: Dict[str, Any], bid: str) -> bool:
    if user.get("is_master"):
        return True
    entry = get_business_entry(user, bid)
    if not entry:
        return False
    return normalize_role(entry.get("role")) in ADMIN_ROLES


def scope_has_mappings(scope: Dict[str, Any]) -> bool:
    if not scope:
        return False
    scope_type = str(scope.get("type") or "").lower()
    if scope_type == "team":
        groups = [g for g in (scope.get("groupnames") or []) if g]
        agents = [a for a in (scope.get("agent_names") or []) if a]
        phones = [p for p in (scope.get("agent_phones") or []) if p]
        exts = [e for e in (scope.get("agent_extensions") or []) if e]
        return bool(groups or agents or phones or exts)
    if scope_type == "own_agent":
        agents = [a for a in (scope.get("agent_names") or []) if a]
        phones = [p for p in (scope.get("agent_phones") or []) if p]
        exts = [e for e in (scope.get("agent_extensions") or []) if e]
        return bool(agents or phones or exts)
    return False


def should_apply_data_scope(user: Dict[str, Any], bid: str, auth_handler: AuthHandler) -> bool:
    """True when SQL/row filters should restrict visible data."""
    if user.get("is_master"):
        return False
    ctx = resolve_business_context(user, bid, auth_handler, reload=bool(user.get("id")))
    role = ctx.get("role")
    if role in FULL_ACCESS_ROLES:
        return False
    if role not in SCOPED_ROLES:
        return False
    scope = ctx.get("scope") or {}
    if rbac_strict():
        return True
    return scope_has_mappings(scope)


def build_raw_call_scope_sql(
    scope: Dict[str, Any],
    *,
    alias: str = "r",
) -> Tuple[str, List[Any]]:
    """
    Build AND (...) SQL fragment for raw_calls row filter.
    Returns ("", []) when scope is business-wide / all.
    """
    scope_type = str(scope.get("type") or "business").lower()
    if scope_type in ("all", "business", "none"):
        if scope_type == "none":
            return "1=0", []
        return "", []

    parts: List[str] = []
    params: List[Any] = []

    def _in_clause(column: str, values: List[Any]) -> None:
        clean = [v for v in values if v]
        if not clean:
            return
        placeholders = ", ".join(["%s"] * len(clean))
        parts.append(f"TRIM(CAST({alias}.{column} AS CHAR)) IN ({placeholders})")
        params.extend([str(v).strip() for v in clean])

    if scope_type == "team":
        groups = [g for g in (scope.get("groupnames") or []) if g]
        if groups:
            placeholders = ", ".join(["%s"] * len(groups))
            parts.append(f"TRIM(CAST({alias}.groupname AS CHAR)) IN ({placeholders})")
            params.extend(groups)
        _in_clause("agentname", scope.get("agent_names") or [])
        _in_clause("agent_callinfo", (scope.get("agent_phones") or []) + (scope.get("agent_extensions") or []))
    elif scope_type == "own_agent":
        _in_clause("agentname", scope.get("agent_names") or [])
        _in_clause("agent_callinfo", (scope.get("agent_phones") or []) + (scope.get("agent_extensions") or []))

    if not parts:
        if rbac_strict():
            return "1=0", []
        return "", []

    if len(parts) == 1:
        return parts[0], params
    return "(" + " OR ".join(parts) + ")", params


def call_record_in_scope(row: Dict[str, Any], scope: Dict[str, Any]) -> bool:
    if not row:
        return False
    scope_type = str(scope.get("type") or "business").lower()
    if scope_type in ("all", "business"):
        return True
    if scope_type == "none":
        return False

    agent_name = str(row.get("agentname") or row.get("agent_name") or "").strip()
    agent_callinfo = str(row.get("agent_callinfo") or "").strip()
    groupname = str(row.get("groupname") or "").strip()

    if scope_type == "team":
        groups = {str(g).strip() for g in (scope.get("groupnames") or []) if g}
        names = {str(n).strip() for n in (scope.get("agent_names") or []) if n}
        phones = {str(p).strip() for p in (scope.get("agent_phones") or []) if p}
        exts = {str(e).strip() for e in (scope.get("agent_extensions") or []) if e}
        identifiers = phones | exts
        if groups and groupname in groups:
            return True
        if names and agent_name in names:
            return True
        if identifiers and agent_callinfo in identifiers:
            return True
        return False

    if scope_type == "own_agent":
        names = {str(n).strip() for n in (scope.get("agent_names") or []) if n}
        phones = {str(p).strip() for p in (scope.get("agent_phones") or []) if p}
        exts = {str(e).strip() for e in (scope.get("agent_extensions") or []) if e}
        identifiers = phones | exts
        if names and agent_name in names:
            return True
        if identifiers and agent_callinfo in identifiers:
            return True
        return False

    return True


def effective_agent_names_for_scope(
    user: Dict[str, Any],
    bid: str,
    auth_handler: AuthHandler,
    requested_agent_names: Optional[str] = None,
) -> Optional[str]:
    """Return comma-separated agent names when scope should default the agent filter."""
    if requested_agent_names:
        return requested_agent_names
    if not should_apply_data_scope(user, bid, auth_handler):
        return None
    scope = resolve_business_context(user, bid, auth_handler, reload=True).get("scope") or {}
    names = [str(n).strip() for n in (scope.get("agent_names") or []) if n]
    if not names:
        return None
    return ",".join(names)


def effective_groupname_for_scope(
    user: Dict[str, Any],
    bid: str,
    auth_handler: AuthHandler,
    requested_groupname: Optional[str],
) -> Optional[str]:
    """
    For managers with a single assigned group and no explicit filter, default to that group.
    Otherwise pass through requested_groupname unchanged.
    """
    if requested_groupname:
        return requested_groupname
    if not should_apply_data_scope(user, bid, auth_handler):
        return None
    ctx = resolve_business_context(user, bid, auth_handler)
    scope = ctx.get("scope") or {}
    if str(scope.get("type")).lower() != "team":
        return None
    groups = [g for g in (scope.get("groupnames") or []) if g]
    if len(groups) == 1:
        return groups[0]
    return None
