"""IAM API views — users, roles, permissions management."""
from __future__ import annotations

import re

from django.contrib.auth import get_user_model
from django.db import models as django_models
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.iam.models import Permission, Role, UserRole
from simorgh.core.context import current_request_context

User = get_user_model()
_MOBILE_RE = re.compile(r"^\+[1-9]\d{6,14}$")


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _serialize_user(user, *, include_roles: bool = True) -> dict:
    mc = getattr(user, "mobile_country", None)
    data: dict = {
        "id": user.pk,
        "mobile": getattr(user, "mobile", None),
        "email": user.email or None,
        "display_name": user.username,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "is_active": user.is_active,
        "is_staff": user.is_staff,
        "date_joined": user.date_joined.isoformat() if user.date_joined else None,
        "last_login": user.last_login.isoformat() if user.last_login else None,
        "mobile_country": {
            "id": mc.pk,
            "iso2": mc.iso2,
            "dial_code": mc.dial_code,
            "flag_emoji": mc.flag_emoji,
        } if mc is not None else None,
    }
    if include_roles:
        assignments = list(user.iam_user_roles.select_related("role").all())
        data["roles"] = [
            {"id": str(a.role.pk), "code": a.role.code, "name": a.role.name, "is_system": a.role.is_system}
            for a in assignments
        ]
    return data


def _serialize_role(role: Role, *, include_permissions: bool = False) -> dict:
    data: dict = {
        "id": str(role.pk),
        "code": role.code,
        "name": role.name,
        "description": role.description,
        "is_system": role.is_system,
        "tenant_id": role.tenant_id,
        "parent_role_id": str(role.parent_role_id) if role.parent_role_id else None,
        "permissions_count": role.permissions.count(),
    }
    if include_permissions:
        data["permissions"] = list(
            role.permissions.values("id", "codename", "label", "module", "resource", "action")
        )
    return data


def _serialize_permission(perm: Permission) -> dict:
    return {
        "id": perm.pk,
        "codename": perm.codename,
        "label": perm.label,
        "module": perm.module,
        "resource": perm.resource,
        "action": perm.action,
        "description": perm.description,
    }


# ---------------------------------------------------------------------------
# Users
# ---------------------------------------------------------------------------

def _has_iam_perm(request: Request, perm: str) -> bool:
    """Return True if the requesting user holds the given IAM permission in the current tenant.

    Checks the pre-resolved ``RequestContext.permissions`` frozenset (built by
    ``RequestContextMiddleware`` from the user's active memberships) so it works
    with any role — standard or custom — as long as the role carries the
    required permission.
    """
    ctx = current_request_context()
    if ctx.is_superuser:
        return True
    return perm in ctx.permissions


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def users_list(request: Request) -> Response:
    """GET: list users (paginated, ?q=). POST: create a new user."""
    # Allow Django staff (superusers) AND users with iam.user.view permission.
    if not request.user.is_staff and not _has_iam_perm(request, "iam.user.view"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    tenant = getattr(request, "tenant", None)

    if request.method == "POST":
        data = request.data
        mobile = str(data.get("mobile", "")).strip()
        password = str(data.get("password", ""))
        if not mobile:
            return Response({"error": {"code": "missing_mobile", "message": "mobile is required."}}, status=status.HTTP_400_BAD_REQUEST)
        if not _MOBILE_RE.match(mobile):
            return Response({"error": {"code": "invalid_mobile", "message": "Mobile must be E.164 format, e.g. +989123456789."}}, status=status.HTTP_400_BAD_REQUEST)
        if User.objects.filter(mobile=mobile).exists():
            return Response({"error": {"code": "mobile_exists", "message": "A user with this mobile already exists."}}, status=status.HTTP_400_BAD_REQUEST)
        if not password or len(password) < 8:
            return Response({"error": {"code": "weak_password", "message": "Password must be at least 8 characters."}}, status=status.HTTP_400_BAD_REQUEST)
        email = str(data.get("email", "")).strip() or None
        if email and User.objects.filter(email=email).exists():
            return Response({"error": {"code": "email_exists", "message": "A user with this email already exists."}}, status=status.HTTP_400_BAD_REQUEST)
        user = User(
            mobile=mobile,
            email=email,
            username=str(data.get("display_name", "")).strip()[:150],
            first_name=str(data.get("first_name", "")).strip()[:150],
            last_name=str(data.get("last_name", "")).strip()[:150],
            is_active=bool(data.get("is_active", True)),
            is_staff=bool(data.get("is_staff", False)),
        )
        user.set_password(password)
        user.save()

        # Auto-enroll the new user into this tenant's default membership so
        # they can log in and land on a tenant after their first session.
        tenant = getattr(request, "tenant", None)
        if tenant is not None:
            from simorgh.apps.memberships.models import Membership, MembershipStatus

            # Prefer least-privileged role so the admin can elevate later.
            _ROLE_PRIORITY = ["tenant_viewer", "tenant_staff", "tenant_manager", "tenant_admin"]
            default_membership = None
            for role_code in _ROLE_PRIORITY:
                m = Membership.objects.filter(
                    tenant=tenant,
                    status=MembershipStatus.ACTIVE,
                    role__code=role_code,
                ).first()
                if m:
                    default_membership = m
                    break
            if default_membership is None:
                # Fallback: any active membership in the tenant.
                default_membership = Membership.objects.filter(
                    tenant=tenant, status=MembershipStatus.ACTIVE
                ).first()
            if default_membership is not None:
                default_membership.users.add(user)

        return Response(_serialize_user(user), status=status.HTTP_201_CREATED)

    # GET — scope to current tenant so users only see members of their own org.
    q = request.query_params.get("q", "").strip()
    page = max(1, int(request.query_params.get("page", 1) or 1))
    page_size = 20

    from django.db.models import Q

    if tenant is not None:
        from simorgh.apps.accounts.selectors import list_users_for_tenant
        qs = list_users_for_tenant(tenant.pk).select_related("mobile_country").prefetch_related(
            "iam_user_roles__role"
        ).order_by("-date_joined")
    else:
        # Fallback for superusers without a tenant context.
        qs = User.objects.select_related("mobile_country").prefetch_related(
            "iam_user_roles__role"
        ).order_by("-date_joined")

    if q:
        qs = qs.filter(Q(mobile__icontains=q) | Q(email__icontains=q) | Q(username__icontains=q))

    total = qs.count()
    users = qs[(page - 1) * page_size: page * page_size]
    return Response({
        "count": total,
        "page": page,
        "page_size": page_size,
        "results": [_serialize_user(u) for u in users],
    })


@api_view(["GET", "PATCH"])
@permission_classes([IsAuthenticated])
def user_detail(request: Request, pk: int) -> Response:
    """GET or PATCH a single user. Staff or iam.user.view holders only."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.user.view"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    try:
        user = User.objects.select_related("mobile_country").prefetch_related(
            "iam_user_roles__role"
        ).get(pk=pk)
    except User.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "PATCH":
        data = request.data
        update_fields: list[str] = []

        if "is_active" in data:
            if user.pk == request.user.pk and not data["is_active"]:
                return Response(
                    {"error": {"code": "self_deactivate", "message": "Cannot deactivate yourself."}},
                    status=status.HTTP_400_BAD_REQUEST,
                )
            user.is_active = bool(data["is_active"])
            update_fields.append("is_active")

        if "is_staff" in data:
            if user.pk == request.user.pk:
                return Response(
                    {"error": {"code": "self_demote", "message": "Cannot change your own staff status."}},
                    status=status.HTTP_400_BAD_REQUEST,
                )
            user.is_staff = bool(data["is_staff"])
            update_fields.append("is_staff")

        if "display_name" in data:
            user.username = str(data["display_name"])[:150]
            update_fields.append("username")

        if "first_name" in data:
            user.first_name = str(data["first_name"])[:150]
            update_fields.append("first_name")

        if "last_name" in data:
            user.last_name = str(data["last_name"])[:150]
            update_fields.append("last_name")

        if "email" in data:
            new_email = str(data["email"]).strip() or None
            if new_email != user.email:
                if new_email and User.objects.filter(email=new_email).exclude(pk=user.pk).exists():
                    return Response(
                        {"error": {"code": "email_exists", "message": "Email already in use."}},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                user.email = new_email
                update_fields.append("email")

        if "mobile" in data:
            new_mobile = str(data["mobile"]).strip()
            if new_mobile and new_mobile != user.mobile:
                if not _MOBILE_RE.match(new_mobile):
                    return Response(
                        {"error": {"code": "invalid_mobile", "message": "Mobile must be E.164 format."}},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                if User.objects.filter(mobile=new_mobile).exclude(pk=user.pk).exists():
                    return Response(
                        {"error": {"code": "mobile_exists", "message": "Mobile already in use."}},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                user.mobile = new_mobile
                update_fields.append("mobile")

        if update_fields:
            user.save(update_fields=update_fields)

    return Response(_serialize_user(user))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def user_set_password(request: Request, pk: int) -> Response:
    """Change a user's password. Staff or iam.user.manage holders only."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.user.manage"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)
    try:
        user = User.objects.get(pk=pk)
    except User.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    new_password = str(request.data.get("new_password", "")).strip()
    if not new_password or len(new_password) < 8:
        return Response(
            {"error": {"code": "weak_password", "message": "Password must be at least 8 characters."}},
            status=status.HTTP_400_BAD_REQUEST,
        )
    user.set_password(new_password)
    user.save(update_fields=["password"])
    return Response({"ok": True})


@api_view(["GET", "PUT"])
@permission_classes([IsAuthenticated])
def user_roles_view(request: Request, pk: int) -> Response:
    """GET or PUT (replace) tenant role assignments for a user. Staff or iam.user.manage holders only."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.user.manage"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)
    try:
        user = User.objects.get(pk=pk)
    except User.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    tenant = getattr(request, "tenant", None)

    if request.method == "PUT":
        role_ids = request.data.get("role_ids", [])
        # Only allow roles scoped to this tenant (or global roles)
        role_qs = Role.objects.filter(pk__in=role_ids)
        if tenant is not None:
            role_qs = role_qs.filter(
                django_models.Q(tenant=tenant) | django_models.Q(tenant__isnull=True)
            )
        valid_roles = list(role_qs)
        UserRole.objects.filter(user=user).delete()
        UserRole.objects.bulk_create([UserRole(user=user, role=r) for r in valid_roles])
    assignments = UserRole.objects.filter(user=user).select_related("role")
    return Response([
        {"id": str(a.role.pk), "code": a.role.code, "name": a.role.name, "is_system": a.role.is_system}
        for a in assignments
    ])


# ---------------------------------------------------------------------------
# Roles
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def roles_list(request: Request) -> Response:
    """List roles (paginated) or create a new non-system role."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.role.view"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    if request.method == "POST":
        data = request.data
        code = str(data.get("code", "")).strip()
        name = str(data.get("name", "")).strip()
        if not code or not name:
            return Response(
                {"error": {"code": "missing_fields", "message": "code and name are required."}},
                status=status.HTTP_400_BAD_REQUEST,
            )

        tenant = getattr(request, "tenant", None)

        parent_role = None
        if data.get("parent_role_id"):
            try:
                parent_role = Role.objects.get(pk=data["parent_role_id"])
            except Role.DoesNotExist:
                return Response(
                    {"error": {"code": "invalid_parent", "message": "Parent role not found."}},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        role = Role.objects.create(
            tenant=tenant,
            code=code,
            name=name,
            description=str(data.get("description", "")),
            parent_role=parent_role,
            is_system=False,
        )

        perm_ids = data.get("permission_ids", [])
        if perm_ids:
            perms = Permission.objects.filter(pk__in=perm_ids)
            role.permissions.set(perms)

        return Response(_serialize_role(role, include_permissions=True), status=status.HTTP_201_CREATED)

    # GET — paginated, tenant-scoped
    tenant = getattr(request, "tenant", None)
    qs = Role.objects.prefetch_related("permissions")

    if tenant is not None:
        qs = qs.filter(
            django_models.Q(tenant=tenant) | django_models.Q(tenant__isnull=True)
        )
    qs = qs.order_by("tenant_id", "code")

    # Pagination
    try:
        page = max(1, int(request.query_params.get("page", 1)))
    except (ValueError, TypeError):
        page = 1
    try:
        page_size = max(1, min(200, int(request.query_params.get("page_size", 20))))
    except (ValueError, TypeError):
        page_size = 20

    total = qs.count()
    offset = (page - 1) * page_size
    roles = qs[offset : offset + page_size]

    return Response({
        "count": total,
        "page": page,
        "page_size": page_size,
        "results": [_serialize_role(r) for r in roles],
    })


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def role_detail(request: Request, pk: str) -> Response:
    """GET, PATCH or DELETE a role. Scoped to current tenant + global roles."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.role.view"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    tenant = getattr(request, "tenant", None)
    qs = Role.objects.prefetch_related("permissions")
    if tenant is not None:
        qs = qs.filter(
            django_models.Q(tenant=tenant) | django_models.Q(tenant__isnull=True)
        )

    try:
        role = qs.get(pk=pk)
    except Role.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "DELETE":
        if role.is_system:
            return Response(
                {"error": {"code": "system_role", "message": "Cannot delete a system role."}},
                status=status.HTTP_400_BAD_REQUEST,
            )
        role.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    if request.method == "PATCH":
        data = request.data
        if role.is_system and ("code" in data or "name" in data):
            return Response(
                {"error": {"code": "system_role", "message": "Cannot rename a system role."}},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if "name" in data:
            role.name = str(data["name"])[:255]
        if "description" in data:
            role.description = str(data["description"])
        if "permission_ids" in data:
            perms = Permission.objects.filter(pk__in=data["permission_ids"])
            role.permissions.set(perms)
        role.save(update_fields=["name", "description"])

    return Response(_serialize_role(role, include_permissions=True))


# ---------------------------------------------------------------------------
# Permissions
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def permissions_list(request: Request) -> Response:
    """Return all permissions as a flat paginated list."""
    if not request.user.is_staff and not _has_iam_perm(request, "iam.permission.view"):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    qs = Permission.objects.order_by("module", "resource", "action")

    # Pagination
    try:
        page = max(1, int(request.query_params.get("page", 1)))
    except (ValueError, TypeError):
        page = 1
    try:
        page_size = max(1, min(500, int(request.query_params.get("page_size", 100))))
    except (ValueError, TypeError):
        page_size = 100

    total = qs.count()
    offset = (page - 1) * page_size
    perms = qs[offset : offset + page_size]

    return Response({
        "count": total,
        "page": page,
        "page_size": page_size,
        "results": [_serialize_permission(p) for p in perms],
    })
