"""Core API (v1): authentication context, organizations, modules, roles, members, audit.

Every view is protected by TenantPermission / PlatformPermission, filters its queryset to the
validated organization and re-checks object ownership inside the services.
"""
from __future__ import annotations

from django.contrib.auth import authenticate
from django.db import transaction
from django.db.models import Q
from rest_framework import status
from rest_framework.exceptions import NotFound, PermissionDenied
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken

from apps.access import services as access
from apps.access.drf import ORG_HEADER, PlatformPermission, TenantPermission, resolve_tenant
from apps.access.models import Permission, Role
from apps.audit.models import AuditLog
from apps.modules import services as modules
from apps.modules.registry import REGISTRY
from apps.modules.models import Module, ModuleDependency, ModuleRequest, OrganizationModule
from apps.organizations import services as orgs
from apps.organizations.models import Organization, OrganizationMembership
from domain.access_policy import scope_rank
from domain.exceptions import DomainError
from interfaces.api_v1 import serializers as s

ORG_LEVEL = {m: "organization" for m in ("GET", "POST", "PUT", "PATCH", "DELETE")}


class StandardPagination(PageNumberPagination):
    page_size = 25
    page_size_query_param = "page_size"
    max_page_size = 100


def _paginate(request, queryset, serializer_class):
    paginator = StandardPagination()
    page = paginator.paginate_queryset(queryset, request)
    return paginator.get_paginated_response(serializer_class(page, many=True).data)


# ------------------------------------------------------------------ auth & context


class LoginView(APIView):
    permission_classes: list = []
    authentication_classes: list = []

    def post(self, request):
        data = s.LoginSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        user = authenticate(
            request, identifier=data.validated_data["identifier"], password=data.validated_data["password"]
        )
        if user is None:
            return Response(
                {"detail": "Invalid credentials.", "code": "invalid_credentials"},
                status=status.HTTP_401_UNAUTHORIZED,
            )
        refresh = RefreshToken.for_user(user)
        return Response(
            {
                "access": str(refresh.access_token),
                "refresh": str(refresh),
                "user": s.UserBriefSerializer(user).data,
                "organizations": s.OrganizationSerializer(access.accessible_organizations(user), many=True).data,
            }
        )


class MeView(APIView):
    """Who am I, which organizations can I work in, and (with X-Organization) what can I do there."""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        memberships = (
            OrganizationMembership.objects.filter(user=user)
            .select_related("organization")
            .prefetch_related("membership_roles__role")
        )
        payload = {
            "user": s.UserBriefSerializer(user).data,
            "memberships": [
                {
                    "membership_id": m.pk,
                    "organization": s.OrganizationSerializer(m.organization).data,
                    "status": m.status,
                    "roles": [{"id": mr.role_id, "code": mr.role.code, "name": mr.role.name} for mr in m.membership_roles.all()],
                }
                for m in memberships
            ],
            "organizations": s.OrganizationSerializer(access.accessible_organizations(user), many=True).data,
            "context": None,
        }
        if request.META.get(ORG_HEADER):
            ctx = resolve_tenant(request)
            payload["context"] = {
                "organization": s.OrganizationSerializer(ctx.organization).data,
                "via_platform": ctx.via_platform,
                "is_member": any(
                    m.organization_id == ctx.organization.pk and m.status == "active" for m in memberships
                ),
                "permissions": {
                    code: {"scope": grant.scope, "can_delegate": grant.can_delegate}
                    for code, grant in sorted(ctx.effective.permissions.items())
                },
                "modules": sorted(ctx.enabled_modules),
                "navigation": access.build_navigation(ctx),
            }
        return Response(payload)


# ------------------------------------------------------------------ organizations (platform)


class OrganizationListCreateView(APIView):
    permission_classes = [PlatformPermission]
    permission_map = {"GET": "organizations.view", "POST": "organizations.create"}

    def get(self, request):
        ctx = request.platform
        qs = Organization.objects.all()
        if not ctx.has("organizations.view", "all"):
            qs = qs.filter(assigned_staff__membership=ctx.membership)
        return Response(s.OrganizationSerializer(qs.order_by("id"), many=True).data)

    def post(self, request):
        data = s.OrganizationCreateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        manager = data.validated_data.pop("manager", None)
        with transaction.atomic():  # a rejected manager (taken username, weak password) must not leave a half-made organization
            org = orgs.create_organization(actor=request.user, request=request, **data.validated_data)
            if manager:
                orgs.add_first_manager(actor_user=request.user, organization=org, request=request, **manager)
        return Response(s.OrganizationSerializer(org).data, status=status.HTTP_201_CREATED)


class OrganizationDetailView(APIView):
    permission_classes = [TenantPermission]
    org_url_kwarg = "org_id"
    permission_map = {"GET": "organizations.view", "PATCH": "organizations.update"}

    def get(self, request, org_id):
        return Response(s.OrganizationSerializer(request.organization).data)

    def patch(self, request, org_id):
        data = s.OrganizationUpdateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        org = orgs.update_organization(
            actor=request.user, organization=request.organization, request=request, **data.validated_data
        )
        return Response(s.OrganizationSerializer(org).data)


class StaffAssignmentsView(APIView):
    """Which customer organizations a NoTeam staff member is responsible for."""

    permission_classes = [PlatformPermission]
    permission_map = {"PUT": "organizations.update"}

    def put(self, request, membership_id):
        membership = OrganizationMembership.objects.filter(
            pk=membership_id, organization__kind=Organization.Kind.PLATFORM
        ).first()
        if membership is None:
            raise NotFound()
        data = s.StaffAssignmentsSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        orgs.set_staff_assignments(
            actor=request.user, platform_membership=membership,
            organization_ids=data.validated_data["organization_ids"], request=request,
        )
        return Response({"organization_ids": sorted(data.validated_data["organization_ids"])})


# ------------------------------------------------------------------ modules


def _module_state_payload(organization):
    enabled = modules.enabled_module_codes(organization)
    states = {st.module_id: st for st in OrganizationModule.objects.filter(organization=organization)}
    deps: dict[str, list[str]] = {}
    for dep in ModuleDependency.objects.all():
        deps.setdefault(dep.module_id, []).append(dep.depends_on_id)
    latest: dict[str, ModuleRequest] = {}
    for row in ModuleRequest.objects.filter(organization=organization).order_by("id"):
        latest[row.module_id] = row
    result = []
    for module in Module.objects.all():
        state = states.get(module.code)
        spec = REGISTRY.get(module.code)
        last = latest.get(module.code)
        result.append(
            {
                "request_status": last.status if last and module.code not in enabled else None,
                "request_note": last.note if last and last.status == "rejected" and module.code not in enabled else "",
                "menus": [{"key": n.key, "label": n.label_fa, "group": n.group} for n in spec.nav] if spec else [],
                "code": module.code,
                "title": module.title_fa,
                "description": module.description_fa,
                "status": module.status,
                "is_core": module.is_core,
                "depends_on": sorted(deps.get(module.code, [])),
                "enabled": module.code in enabled,
                "enabled_at": state.enabled_at if state else None,
                "disabled_at": state.disabled_at if state else None,
            }
        )
    return result


class OrganizationModulesView(APIView):
    permission_classes = [TenantPermission]
    org_url_kwarg = "org_id"
    permission_map = {"GET": "modules.view"}

    def get(self, request, org_id):
        return Response(_module_state_payload(request.organization))


class OrganizationModuleToggleView(APIView):
    permission_classes = [TenantPermission]
    org_url_kwarg = "org_id"
    permission_map = {"POST": "modules.manage"}
    enable = True

    def post(self, request, org_id, code):
        action = modules.enable_module if self.enable else modules.disable_module
        action(actor=request.user, organization=request.organization, module_code=code, request=request)
        return Response(_module_state_payload(request.organization))

class SettingsView(APIView):
    """Organization-wide settings. Everyone reads them through /me/; only a manager changes them."""

    permission_classes = [TenantPermission]
    permission_map = {"GET": "settings.view", "PATCH": "settings.manage"}
    scope_map = {"GET": "organization", "PATCH": "organization"}

    def get(self, request):
        return Response(s.OrganizationSerializer(request.organization).data)

    def patch(self, request):
        if request.organization.is_platform and "name" in request.data:
            raise DomainError("The platform organization cannot be renamed here.")
        org = orgs.update_settings(
            actor=request.user, organization=request.organization, request=request,
            accent_color=request.data.get("accent_color"), name=request.data.get("name"),
        )
        return Response(s.OrganizationSerializer(org).data)


class SettingsLogoView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"POST": "settings.manage", "DELETE": "settings.manage"}
    scope_map = {"POST": "organization", "DELETE": "organization"}

    def post(self, request):
        if request.organization.is_platform:
            raise DomainError("Only customer organizations have a logo.")
        org = orgs.set_logo(
            actor=request.user, organization=request.organization, upload=request.FILES.get("logo"), request=request
        )
        return Response(s.OrganizationSerializer(org).data)

    def delete(self, request):
        org = orgs.remove_logo(actor=request.user, organization=request.organization, request=request)
        return Response(s.OrganizationSerializer(org).data)


class OrganizationModuleRequestView(APIView):
    """A manager asks NoTeam to enable a module for their organization."""

    permission_classes = [TenantPermission]
    org_url_kwarg = "org_id"
    permission_map = {"POST": "modules.view"}
    scope_map = {"POST": "organization"}

    def post(self, request, org_id, code):
        modules.request_module(actor=request.user, organization=request.organization, module_code=code, request=request)
        return Response(_module_state_payload(request.organization), status=status.HTTP_201_CREATED)


def _module_request_payload(row: ModuleRequest) -> dict:
    return {
        "id": row.pk,
        "organization": {"id": row.organization_id, "name": row.organization.name, "slug": row.organization.slug},
        "module": {"code": row.module_id, "title": row.module.title_fa},
        "requested_by": {"id": row.requested_by_id, "name": row.requested_by.name, "username": row.requested_by.username}
        if row.requested_by else None,
        "status": row.status,
        "note": row.note,
        "created_at": row.created_at,
        "decided_at": row.decided_at,
        "decided_by": {"id": row.decided_by_id, "name": row.decided_by.name, "username": row.decided_by.username}
        if row.decided_by else None,
    }


class PlatformModuleRequestListView(APIView):
    permission_classes = [PlatformPermission]
    permission_map = {"GET": "modules.manage"}

    def get(self, request):
        qs = ModuleRequest.objects.select_related("organization", "module", "requested_by", "decided_by")
        wanted = request.query_params.get("status") or "pending"
        if wanted == "decided":  # the history: everything that was approved or rejected
            qs = qs.exclude(status=ModuleRequest.Status.PENDING).order_by("-decided_at", "-id")
        elif wanted != "all":
            qs = qs.filter(status=wanted)
        return Response([_module_request_payload(r) for r in qs[:200]])


class PlatformModuleRequestDecisionView(APIView):
    permission_classes = [PlatformPermission]
    permission_map = {"POST": "modules.manage"}
    approve = True

    def post(self, request, pk):
        row = ModuleRequest.objects.select_related("organization", "module", "requested_by").filter(pk=pk).first()
        if row is None:
            raise NotFound()
        if not self.approve and not str(request.data.get("note") or "").strip():
            raise DomainError("A reason is required to reject a request.")
        modules.decide_module_request(
            actor=request.user, module_request=row, approve=self.approve,
            note=str(request.data.get("note") or ""), request=request,
        )
        return Response(_module_request_payload(row))


class PlatformDirectoryView(APIView):
    """Every organization with its members, grouped organization by organization (NoTeam staff only)."""

    permission_classes = [PlatformPermission]
    permission_map = {"GET": "organizations.view"}

    def get(self, request):
        search = (request.query_params.get("search") or "").strip().lower()
        organizations = access.accessible_organizations(request.user)
        groups = []
        for org in organizations:
            rows = (
                OrganizationMembership.objects.filter(organization=org)
                .select_related("user").prefetch_related("membership_roles__role").order_by("user__name", "id")
            )
            members, managers = [], []
            for m in rows:
                u = m.user
                is_manager = any(mr.role.code == "org_manager" for mr in m.membership_roles.all())
                contact = {"name": u.name, "username": u.username, "mobile": u.mobile}
                if is_manager and m.status == "active":
                    managers.append(contact)  # shown in the header whatever the search matched
                haystack = " ".join(str(v or "") for v in (u.username, u.name, u.mobile, m.personnel_code)).lower()
                if search and search not in haystack:
                    continue
                members.append(
                    {
                        "membership_id": m.pk,
                        "user": {"id": u.pk, "username": u.username, "name": u.name, "mobile": u.mobile, "is_active": u.is_active},
                        "personnel_code": m.personnel_code,
                        "status": m.status,
                        "is_manager": is_manager,
                        "roles": [mr.role.name for mr in m.membership_roles.all()],
                    }
                )
            members.sort(key=lambda row: not row["is_manager"])  # managers first, the rest keep their name order
            if members or not search:
                groups.append(
                    {
                        "organization": {
                            "id": org.pk, "name": org.name, "slug": org.slug, "kind": org.kind, "is_active": org.is_active,
                        },
                        "managers": managers,
                        "members": members,
                    }
                )
        return Response(groups)


# ------------------------------------------------------------------ permission catalog & roles


class PermissionCatalogView(APIView):
    """Permissions that can actually be granted in the selected organization."""

    permission_classes = [TenantPermission]
    permission_map = {"GET": "roles.view"}

    def get(self, request):
        org = request.organization
        qs = Permission.objects.filter(module_id__in=request.tenant.enabled_modules)
        if not org.is_platform:
            qs = qs.filter(is_platform_only=False)
        return Response(s.PermissionSerializer(qs.order_by("code"), many=True).data)


class RoleListCreateView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "roles.view", "POST": "roles.manage"}
    scope_map = ORG_LEVEL

    def get(self, request):
        qs = Role.objects.filter(organization=request.organization).prefetch_related("grants__permission")
        return Response(s.RoleSerializer(qs, many=True).data)

    def post(self, request):
        data = s.RoleCreateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        role = access.create_role(actor=request.tenant, request=request, **data.validated_data)
        return Response(s.RoleSerializer(role).data, status=status.HTTP_201_CREATED)


def _get_role(request, pk) -> Role:
    role = Role.objects.filter(organization=request.organization, pk=pk).first()
    if role is None:
        raise NotFound()
    return role


class RoleDetailView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "roles.view", "PATCH": "roles.manage", "DELETE": "roles.manage"}
    scope_map = ORG_LEVEL

    def get(self, request, pk):
        return Response(s.RoleSerializer(_get_role(request, pk)).data)

    def patch(self, request, pk):
        data = s.RoleUpdateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        role = access.update_role(
            actor=request.tenant, role=_get_role(request, pk), request=request,
            name=data.validated_data.get("name"), is_active=data.validated_data.get("is_active"),
        )
        return Response(s.RoleSerializer(role).data)

    def delete(self, request, pk):
        access.delete_role(actor=request.tenant, role=_get_role(request, pk), request=request)
        return Response(status=status.HTTP_204_NO_CONTENT)


class RolePermissionsView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"PUT": "roles.manage"}
    scope_map = ORG_LEVEL

    def put(self, request, pk):
        data = s.RoleGrantsSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        role = access.set_role_permissions(
            actor=request.tenant, role=_get_role(request, pk), grants=data.validated_data["grants"], request=request
        )
        return Response(s.RoleSerializer(role).data)


# ------------------------------------------------------------------ members


def _members_queryset(request):
    """Members visible to the caller. Scopes below `organization` see only themselves
    (team/unit scopes become wider once OrganizationUnit/Team exist)."""
    ctx = request.tenant
    qs = (
        OrganizationMembership.objects.filter(organization=request.organization)
        .select_related("user")
        .prefetch_related("membership_roles__role")
    )
    scope = ctx.effective.scope_of("members.view") or "self"
    if scope_rank(scope) < scope_rank("organization"):
        qs = qs.filter(pk=ctx.membership.pk)
    return qs


def _get_member(request, pk, *, for_write: bool = False) -> OrganizationMembership:
    qs = _members_queryset(request)
    if for_write and not request.tenant.has("members.view", "organization"):
        raise PermissionDenied("You do not have permission for this operation.", code="permission_denied")
    member = qs.filter(pk=pk).first()
    if member is None:
        raise NotFound()
    return member


class MemberListCreateView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "members.view", "POST": "members.create"}
    scope_map = {"POST": "organization"}

    def get(self, request):
        qs = _members_queryset(request)
        term = request.query_params.get("search", "").strip()
        if term:
            qs = qs.filter(
                Q(user__username__icontains=term) | Q(user__name__icontains=term)
                | Q(user__mobile__icontains=term) | Q(personnel_code__icontains=term)
            )
        if request.query_params.get("status"):
            qs = qs.filter(status=request.query_params["status"])
        return _paginate(request, qs.order_by("id"), s.MemberSerializer)

    def post(self, request):
        data = s.MemberCreateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        membership = orgs.add_member(actor=request.tenant, request=request, **data.validated_data)
        return Response(s.MemberSerializer(membership).data, status=status.HTTP_201_CREATED)


class MemberLinkView(APIView):
    """NoTeam staff only: attach an existing login to the selected organization."""

    permission_classes = [TenantPermission]
    permission_map = {"POST": "members.link"}
    scope_map = ORG_LEVEL

    def post(self, request):
        data = s.MemberLinkSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        membership = orgs.link_existing_member(actor=request.tenant, request=request, **data.validated_data)
        return Response(s.MemberSerializer(membership).data, status=status.HTTP_201_CREATED)


class MemberDetailView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "members.view", "PATCH": "members.update"}
    scope_map = {"PATCH": "organization"}

    def get(self, request, pk):
        return Response(s.MemberSerializer(_get_member(request, pk)).data)

    def patch(self, request, pk):
        member = _get_member(request, pk, for_write=True)
        data = s.MemberUpdateSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        new_status = data.validated_data.get("status")
        if new_status and new_status != OrganizationMembership.Status.ACTIVE and not request.tenant.has(
            "members.delete", "organization"
        ):
            raise PermissionDenied("You do not have permission for this operation.", code="permission_denied")
        if new_status:
            orgs.set_member_status(actor=request.tenant, membership=member, status=new_status, request=request)
        fields = {k: v for k, v in data.validated_data.items() if k != "status"}
        if fields:
            orgs.update_member(actor=request.tenant, membership=member, data=fields, request=request)
        member.refresh_from_db()
        return Response(s.MemberSerializer(member).data)


class MemberRolesView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"PUT": "access.manage"}
    scope_map = ORG_LEVEL

    def put(self, request, pk):
        member = _get_member(request, pk, for_write=True)
        data = s.MemberRolesSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        ids = data.validated_data["role_ids"]
        roles = list(Role.objects.filter(organization=request.organization, pk__in=ids))
        if len(roles) != len(set(ids)):
            raise NotFound()
        access.set_member_roles(actor=request.tenant, target=member, roles=roles, request=request)
        member = _get_member(request, pk)
        return Response(s.MemberSerializer(member).data)


class MemberOverridesView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "access.manage", "PUT": "access.manage"}
    scope_map = ORG_LEVEL

    def get(self, request, pk):
        member = _get_member(request, pk, for_write=True)
        return Response(s.OverrideSerializer(member.overrides.select_related("permission"), many=True).data)

    def put(self, request, pk):
        member = _get_member(request, pk, for_write=True)
        data = s.OverrideInputSerializer(data=request.data)
        data.is_valid(raise_exception=True)
        v = data.validated_data
        override = access.set_override(
            actor=request.tenant, target=member, permission_code=v["permission"], effect=v["effect"],
            scope=v["scope"], can_delegate=v["can_delegate"], reason=v["reason"],
            expires_at=v["expires_at"], request=request,
        )
        return Response(s.OverrideSerializer(override).data)


class MemberOverrideDeleteView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"DELETE": "access.manage"}
    scope_map = ORG_LEVEL

    def delete(self, request, pk, code):
        member = _get_member(request, pk, for_write=True)
        access.remove_override(actor=request.tenant, target=member, permission_code=code, request=request)
        return Response(status=status.HTTP_204_NO_CONTENT)


# ------------------------------------------------------------------ audit


class AuditLogListView(APIView):
    permission_classes = [TenantPermission]
    permission_map = {"GET": "audit.view"}
    scope_map = ORG_LEVEL

    def get(self, request):
        qs = AuditLog.objects.filter(organization=request.organization).select_related("actor")
        params = request.query_params
        if params.get("action"):
            qs = qs.filter(action=params["action"])
        if params.get("entity_type"):
            qs = qs.filter(entity_type=params["entity_type"])
        if params.get("actor"):
            qs = qs.filter(actor_id=params["actor"])
        return _paginate(request, qs.order_by("-id"), s.AuditLogSerializer)
