import base64
import uuid

from django.contrib.contenttypes.models import ContentType
from django.core.files.base import ContentFile
from rest_framework import serializers
from .models import User, Tag, Attachment, Comment, Notification, AuditLog, AppConfig


class Base64ImageField(serializers.ImageField):
    """ImageField that also accepts a base64 data-URI string, so clients that
    can't send multipart (e.g. React Native on the new arch) can upload an
    avatar inside a normal JSON body. Regular multipart file uploads still work.
    Pillow (via ImageField validation) rejects anything that isn't a real image.
    """

    def to_internal_value(self, data):
        if isinstance(data, str) and data.startswith("data:image"):
            header, _, b64 = data.partition(",")
            # header looks like "data:image/png;base64" — pull the subtype for the ext.
            mime = header[len("data:"):].split(";", 1)[0]  # e.g. "image/png"
            ext = (mime.split("/", 1)[1] or "jpg").lower()
            if ext == "jpeg":
                ext = "jpg"
            try:
                decoded = base64.b64decode(b64)
            except (TypeError, ValueError):
                self.fail("invalid_image")
            data = ContentFile(decoded, name=f"{uuid.uuid4().hex}.{ext}")
        return super().to_internal_value(data)


class UserSerializer(serializers.ModelSerializer):
    """Serializer for User model. Includes RBAC roles + flat permissions."""
    is_superuser = serializers.BooleanField(read_only=True)
    avatar = Base64ImageField(required=False, allow_null=True)
    roles = serializers.SerializerMethodField()
    permissions = serializers.SerializerMethodField()  # deprecated alias — v2 clients read `capabilities`
    capabilities = serializers.SerializerMethodField()
    access_version = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = ["id", "email", "username", "first_name", "last_name", "avatar", "phone", "bio",
                  "must_change_password", "onboarding_completed", "is_superuser", "organization_role",
                  "donor_role", "roles", "permissions", "capabilities", "access_version",
                  "created_at", "updated_at"]
        read_only_fields = ["id", "must_change_password", "onboarding_completed",
                            "is_superuser", "organization_role", "donor_role", "roles", "permissions",
                            "capabilities", "access_version", "created_at", "updated_at"]

    def _active_organization(self):
        request = self.context.get("request")
        if request is None:
            return None
        from .tenancy import get_active_organization
        try:
            return get_active_organization(request)
        except Exception:
            return None

    def get_roles(self, obj):
        # Read from the prefetched user_roles__role cache (set on the viewset
        # queryset) — no inline select_related, which would bypass the cache
        # and re-query per row. Filtered to the caller's active org so a user
        # in multiple orgs only sees roles held in the one they're acting as.
        organization = self._active_organization()
        if organization is None:
            return []
        return [
            {"id": ur.role.id, "slug": ur.role.slug, "name": ur.role.name}
            for ur in obj.user_roles.all() if ur.organization_id == organization.id
        ]

    def get_permissions(self, obj):
        if obj.is_superuser:
            return ["*"]
        organization = self._active_organization()
        if organization is None:
            return []
        # Compute from the prefetched user_roles__role__permissions cache to
        # avoid a per-user query. Falls back to the model method when the
        # relations weren't prefetched (e.g. detail/create responses).
        if "user_roles" in getattr(obj, "_prefetched_objects_cache", {}):
            codes = {
                f"{p.domain}.{p.action}"
                for ur in obj.user_roles.all() if ur.organization_id == organization.id
                for p in ur.role.permissions.all()
                # v2 capability rows are published via `capabilities`, not here
                if not p.code or p.code.startswith("legacy.")
            }
            return sorted(codes)
        return sorted(obj._role_permission_codenames(organization))

    def get_capabilities(self, obj):
        # Active-organization v2 capability codes, sorted for stable caching.
        # Presentation only — object authorization stays source-bound server-side.
        organization = self._active_organization()
        if organization is None:
            return []
        from .authorization import effective_capabilities
        return sorted(effective_capabilities(obj, organization))

    def get_access_version(self, obj):
        return 2


class TagSerializer(serializers.ModelSerializer):
    """Serializer for Tag model."""

    class Meta:
        model = Tag
        fields = ["id", "slug", "name", "color", "created_at"]
        read_only_fields = ["id", "created_at"]


class AttachmentSerializer(serializers.ModelSerializer):
    """Serializer for Attachment model."""

    class Meta:
        model = Attachment
        fields = ["id", "file", "name", "size", "mime_type", "uploaded_by", "created_at"]
        read_only_fields = ["id", "created_at"]


class CommentSerializer(serializers.ModelSerializer):
    """Serializer for Comment model. Attaches to any model via a generic
    relation — clients pass `model` as "app_label.model" (e.g.
    "projects.projectfund") instead of a raw numeric ContentType id."""
    author_name = serializers.SerializerMethodField()
    model = serializers.CharField(write_only=True)

    class Meta:
        model = Comment
        fields = ["id", "content", "model", "object_id", "author", "author_name", "created_at", "updated_at"]
        read_only_fields = ["id", "author", "created_at", "updated_at"]

    def get_author_name(self, obj):
        return obj.author.get_full_name() or obj.author.username

    def validate_model(self, value):
        try:
            app_label, model = value.split(".", 1)
            return ContentType.objects.get_by_natural_key(app_label, model)
        except (ValueError, ContentType.DoesNotExist):
            raise serializers.ValidationError('Use "app_label.model", e.g. "projects.projectfund".')

    def validate_object_id(self, value):
        if self.instance is not None and str(value) != str(self.instance.object_id):
            raise serializers.ValidationError("A comment's target cannot be changed.")
        return value

    def create(self, validated_data):
        validated_data["content_type"] = validated_data.pop("model")
        return super().create(validated_data)


class NotificationSerializer(serializers.ModelSerializer):
    """Serializer for Notification model."""

    class Meta:
        model = Notification
        fields = ["id", "user", "title", "message", "is_read", "link", "created_at"]
        read_only_fields = ["id", "created_at"]


class AuditLogSerializer(serializers.ModelSerializer):
    """Serializer for AuditLog model."""

    user_name = serializers.SerializerMethodField()

    class Meta:
        model = AuditLog
        fields = ["id", "user", "user_name", "action", "model_name", "object_id", "changes", "ip_address", "created_at"]
        read_only_fields = ["id", "created_at"]

    def get_user_name(self, obj):
        return obj.user.get_full_name() or obj.user.username if obj.user else None


class AppConfigSerializer(serializers.ModelSerializer):
    """System config. API keys + email password are write-only; a `*_set`
    boolean signals whether a secret is stored. Submitting a blank secret keeps
    the existing value (so the UI never has to round-trip the actual key)."""
    deepseek_api_key = serializers.CharField(write_only=True, required=False, allow_blank=True)
    minimax_api_key = serializers.CharField(write_only=True, required=False, allow_blank=True)
    email_password = serializers.CharField(write_only=True, required=False, allow_blank=True)

    deepseek_api_key_set = serializers.SerializerMethodField()
    minimax_api_key_set = serializers.SerializerMethodField()
    email_password_set = serializers.SerializerMethodField()

    class Meta:
        model = AppConfig
        fields = [
            "ai_provider",
            "deepseek_api_key", "deepseek_api_key_set", "deepseek_base_url", "deepseek_model",
            "minimax_api_key", "minimax_api_key_set", "minimax_base_url", "minimax_model",
            "email_host", "email_port", "email_username",
            "email_password", "email_password_set", "email_from", "email_use_tls",
            "calendar_default_view",
            "updated_at",
        ]
        read_only_fields = ["updated_at"]

    def get_deepseek_api_key_set(self, obj):
        return bool(obj.deepseek_api_key)

    def get_minimax_api_key_set(self, obj):
        return bool(obj.minimax_api_key)

    def get_email_password_set(self, obj):
        return bool(obj.email_password)

    def update(self, instance, validated_data):
        # Blank secret = leave unchanged.
        for secret in ("deepseek_api_key", "minimax_api_key", "email_password"):
            if secret in validated_data and validated_data[secret] == "":
                validated_data.pop(secret)
        return super().update(instance, validated_data)
