"""
Tab-based permission for shop and admin APIs.
Super admin (is_superuser) has full access. Other staff need the required_tab in allowed_tabs.
"""
from rest_framework import permissions
from account.middleware import get_current_request
from rest_framework.exceptions import PermissionDenied


# Tab keys used in frontend sidebar (must match frontend TAB_KEYS)
TAB_KEYS = [
    "dashboard",
    "shops",
    "users",
    "employees",
    "customers",
    "suppliers",
    "category",
    "units",
    "products",
    "stock",
    "stock_transfer",
    "purchases",
    "sales",
    "quotations",
    "sale_return",
    "purchase_return",
    "purchase_payments",
    "sales_payments",
    "debtors",
    "company_settings",
    "warehouses",
    "currencies",

    "exchange_rates",
    "accounts",
    "account_transactions",
    "account_transfers",
    "payroll",
    "advance",
    "passive_income",
    "expenses",
    "expense_categories",
    "reports",
    "daily_diary",
]

ROLE_PERMISSIONS = {
    "HEAD_USER": ["*"],
    "BRANCH_MANAGER": ["*"],
    "ACCOUNTANT": [
        "finance.receive_customer_payment", "finance.make_supplier_payment",
        "finance.transfer_accounts", "accounting.create_manual_journal",
        "accounting.post_journal", "accounting.void_journal",
        "accounting.view_general_ledger",
        "expenses.create_expense", "expenses.post_expense", "expenses.void_expense",
    ],
    "CASHIER": ["sales.create_sale", "sales.post_sale", "finance.receive_customer_payment"],
    "SALES_USER": ["sales.create_sale", "sales.post_sale", "sales.return_sale"],
    "PURCHASE_USER": ["purchases.create_purchase", "purchases.post_purchase", "purchases.return_purchase"],
    "WAREHOUSE_USER": ["inventory.adjust_stock", "inventory.transfer_stock", "inventory.manual_batch_override"],
    "REPORT_VIEWER": ["accounting.view_general_ledger"],
}


def permissions_for_role(role):
    return list(ROLE_PERMISSIONS.get(role or "", []))


def _selected_shop_id(request=None):
    request = request or get_current_request()
    if request is None:
        return None
    return request.headers.get("X-Selected-Shop") or request.META.get("HTTP_X_SELECTED_SHOP")


def admin_branch_context_active(request=None):
    """True when a super admin deliberately entered a single-branch workspace."""
    request = request or get_current_request()
    if request is None or not _selected_shop_id(request):
        return False
    value = request.headers.get("X-Admin-Branch-Context") or request.META.get(
        "HTTP_X_ADMIN_BRANCH_CONTEXT"
    )
    return str(value or "").strip().lower() in {"1", "true", "yes", "on"}


def accessible_shops(user, request=None):
    """Single source of truth for branches visible in the current request."""
    from django.db.models import Q
    from shop_management.models.data.shop import Shop

    if not user or not user.is_authenticated:
        return Shop.objects.none()
    queryset = Shop.objects.all() if user.is_superuser else Shop.objects.filter(
        Q(owner=user) | Q(assigned_users=user)
    ).distinct()
    selected_shop_id = _selected_shop_id(request)
    if selected_shop_id:
        queryset = queryset.filter(pk=selected_shop_id)
    return queryset


def assert_shop_access(user, shop, request=None):
    """Return the accessible Shop or raise a non-enumerating 403 response."""
    shop_id = getattr(shop, "pk", shop)
    accessible = accessible_shops(user, request).filter(pk=shop_id).first()
    if not accessible:
        raise PermissionDenied("You do not have access to this branch.")
    return accessible


def accessible_warehouses(user, request=None):
    from shop_management.models.data.organization import Warehouse
    return Warehouse.objects.filter(shop__in=accessible_shops(user, request)).distinct()


def assert_warehouse_access(user, warehouse, request=None, active_only=False):
    warehouse_id = getattr(warehouse, "pk", warehouse)
    queryset = accessible_warehouses(user, request)
    if active_only:
        queryset = queryset.filter(is_active=True)
    accessible = queryset.filter(pk=warehouse_id).first()
    if not accessible:
        raise PermissionDenied("You do not have access to this warehouse.")
    return accessible


class IsStaff(permissions.BasePermission):
    """Allow only staff users."""

    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated and request.user.is_staff


def user_can_see_all_shop_data(user, required_tab):
    """
    Super Admin can see everything. Others are limited to their own data.
    """
    if not user or not user.is_authenticated:
        return False
    # Super admins normally retain company-wide visibility. Entering a branch
    # workspace temporarily applies the same branch isolation as branch staff.
    return getattr(user, "is_superuser", False) and not admin_branch_context_active()


def get_user_shop_filter(user):
    """
    Returns a Q object that filters shops for the user.
    Super Admin sees all shops. Others see only their owned or assigned shops.
    """
    from django.db.models import Q
    return Q(pk__in=accessible_shops(user).values("pk"))


def get_user_data_filter(user, shop_field=None):
    """
    Returns a Q object for filtering data that the user should see.
    Super Admin sees all data. Others see records from shops they own or are
    assigned to. Non shop-scoped records still fall back to creator ownership.
    """
    from django.db.models import Q
    if shop_field:
        return Q(**{f"{shop_field}__in": accessible_shops(user).values("pk")})
    if not user or not user.is_authenticated:
        return Q(pk=None)
    if getattr(user, "is_superuser", False):
        return Q()
    return Q(created_by=user)


class HasTabPermission(permissions.BasePermission):
    """
    Allow if user has required_tab in allowed_tabs.
    Superusers still need tab permissions to access the UI, but will only see their own data.
    """

    def has_permission(self, request, view):
        if not request.user or not request.user.is_authenticated:
            return False
        if not request.user.is_staff:
            return False
        
        # Superusers still have UI access but data will be filtered in get_queryset
        if getattr(request.user, "is_superuser", False):
            return True
        required_tabs = getattr(view, "required_tabs", None)
        if required_tabs is not None:
            allowed = getattr(request.user, "allowed_tabs", None) or []
            return any(t in allowed for t in required_tabs)
        required_tab = getattr(view, "required_tab", None)
        if not required_tab:
            return True
        if (
            getattr(request.user, "system_role", "") == "BRANCH_MANAGER"
            and required_tab == "company_settings"
        ):
            return True
        allowed = getattr(request.user, "allowed_tabs", None) or []
        return required_tab in allowed


class HasActionPermission(permissions.BasePermission):
    """Enforce business actions independently from menu/tab visibility."""

    message = "Your assigned role does not allow this action."

    def has_permission(self, request, view):
        user = getattr(request, "user", None)
        if not user or not user.is_authenticated or not user.is_staff:
            return False
        if getattr(user, "is_superuser", False):
            return True
        permission_map = getattr(view, "action_permission_map", {})
        required = permission_map.get(getattr(view, "action", None))
        if not required:
            required = permission_map.get(str(getattr(request, "method", "")).upper())
        if not required:
            return True
        role = getattr(user, "system_role", "") or ""
        assigned = getattr(user, "action_permissions", None) or []
        # Existing accounts remain operational until an administrator assigns a role.
        if not role and not assigned:
            return True
        return "*" in assigned or required in assigned
