from django.db.models import Q

from .permissions import accessible_company_ids, user_can_access_company


class CompanyScopedAdminMixin:
    """Tenant guard for Django Admin.

    ``company_lookup`` locates the company from the model being administered.
    ``fk_company_lookups`` optionally maps FK field names to a company lookup on
    the *target* model for relations that do not contain a direct ``company``
    field (for example AttendanceAdjustment.attendance -> employee -> company).
    """

    company_lookup = "company"
    fk_company_lookups = {}

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        ids = accessible_company_ids(request.user)
        if not ids:
            return qs.none()
        return qs.filter(**{f"{self.company_lookup}__in": ids})

    def _object_company_id(self, obj):
        current = obj
        for part in self.company_lookup.split("__"):
            if part == "pk":
                return getattr(current, "pk", None)
            current = getattr(current, part, None)
            if current is None:
                return None
        return getattr(current, "pk", current)

    def has_view_permission(self, request, obj=None):
        allowed = super().has_view_permission(request, obj)
        return allowed and (obj is None or user_can_access_company(request.user, self._object_company_id(obj)))

    def has_change_permission(self, request, obj=None):
        allowed = super().has_change_permission(request, obj)
        return allowed and (obj is None or user_can_access_company(request.user, self._object_company_id(obj)))

    # HR/payroll history and master records are deactivated/reversed through
    # business workflows. Hard delete from Django Admin is intentionally off,
    # including for superusers, so the admin console cannot bypass audit/history.
    def has_delete_permission(self, request, obj=None):
        return False

    def get_readonly_fields(self, request, obj=None):
        """Keep direct tenant ownership immutable after creation.

        Company-scoped records may be maintained in Admin, but moving an
        existing object to another company would detach its historical child
        data from the original tenant context. This applies to superusers too.
        """
        readonly = list(super().get_readonly_fields(request, obj))
        if obj is not None and any(f.name == "company" for f in obj._meta.fields):
            if "company" not in readonly:
                readonly.append("company")
        return tuple(readonly)

    def _audit_values(self, obj):
        values = {}
        for field in getattr(obj._meta, "concrete_fields", []):
            name = field.name
            if name in {"password"}:
                continue
            try:
                value = getattr(obj, field.attname if getattr(field, "is_relation", False) else name)
            except Exception:
                continue
            if hasattr(value, "name"):
                value = value.name
            if value is None or isinstance(value, (str, int, float, bool)):
                safe = value
            else:
                safe = str(value)
            if isinstance(safe, str) and len(safe) > 500:
                safe = safe[:500] + "…"
            values[name] = safe
        return values

    def save_model(self, request, obj, form, change):
        old_values = {}
        if change and getattr(obj, "pk", None):
            try:
                old_obj = type(obj)._default_manager.get(pk=obj.pk)
                old_values = self._audit_values(old_obj)
            except type(obj).DoesNotExist:
                old_values = {}
        super().save_model(request, obj, form, change)
        try:
            from apps.audit.services import log_event
            log_event(
                user=request.user,
                company=self._object_company_id(obj),
                action="ADMIN_UPDATE" if change else "ADMIN_CREATE",
                module=obj._meta.label_lower,
                object_id=obj.pk,
                object_repr=str(obj),
                old_values=old_values,
                new_values=self._audit_values(obj),
                reason="Django Admin maintenance action",
                request=request,
            )
        except Exception:
            # Audit must never corrupt the admin transaction. Runtime checks and
            # monitoring should surface audit failures separately.
            pass

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if request.user.is_superuser:
            return super().formfield_for_foreignkey(db_field, request, **kwargs)
        ids = accessible_company_ids(request.user)
        model = db_field.remote_field.model
        label = getattr(model._meta, "label_lower", "")
        explicit_lookup = self.fk_company_lookups.get(db_field.name)

        if explicit_lookup:
            kwargs["queryset"] = model.objects.filter(**{f"{explicit_lookup}__in": ids}).distinct()
        elif label == "organization.company":
            kwargs["queryset"] = model.objects.filter(pk__in=ids)
        elif any(f.name == "company" for f in model._meta.fields):
            kwargs["queryset"] = model.objects.filter(company_id__in=ids)
        elif label == "accounts.user":
            # Users may be linked by explicit company access or employee profile.
            kwargs["queryset"] = model.objects.filter(
                Q(company_accesses__company_id__in=ids, company_accesses__is_active=True)
                | Q(employee_profile__company_id__in=ids)
                | Q(pk=request.user.pk)
            ).distinct()
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
