from rest_framework import serializers
from django.contrib.auth import get_user_model
from apps.core.tenancy import get_active_organization
from .models import (
    Conversation,
    ConversationParticipant,
    Message,
    MessageReaction,
    MessageKeyWrap,
    MessageAttachment,
    UserKey,
)

User = get_user_model()


class UserKeySerializer(serializers.ModelSerializer):
    user_email = serializers.EmailField(source='user.email', read_only=True)

    class Meta:
        model = UserKey
        fields = ['user', 'user_email', 'public_key', 'created_at', 'rotated_at']
        read_only_fields = ['user', 'created_at', 'rotated_at']


class MessageKeyWrapSerializer(serializers.ModelSerializer):
    class Meta:
        model = MessageKeyWrap
        fields = ['recipient', 'wrapped_key', 'wrap_nonce']


class MessageAttachmentSerializer(serializers.ModelSerializer):
    download_url = serializers.SerializerMethodField()

    class Meta:
        model = MessageAttachment
        fields = [
            'id', 'message', 'encrypted_filename', 'filename_nonce',
            'file_nonce', 'mime_hint', 'size', 'download_url', 'created_at',
        ]
        read_only_fields = ['id', 'download_url', 'created_at']

    def get_download_url(self, obj):
        if obj.encrypted_file:
            request = self.context.get('request')
            url = obj.encrypted_file.url
            return request.build_absolute_uri(url) if request else url
        return None


class MessageReactionSerializer(serializers.ModelSerializer):
    user_email = serializers.EmailField(source='user.email', read_only=True)

    class Meta:
        model = MessageReaction
        fields = ['id', 'message', 'user', 'user_email', 'reaction', 'created_at']
        read_only_fields = ['id', 'created_at']


class MessageSerializer(serializers.ModelSerializer):
    """Read serializer — exposes only the wrap for the requesting user."""
    sender_email = serializers.EmailField(source='sender.email', read_only=True)
    sender_public_key = serializers.SerializerMethodField()
    reactions = MessageReactionSerializer(many=True, read_only=True)
    reaction_count = serializers.SerializerMethodField()
    my_wrap = serializers.SerializerMethodField()
    attachments = MessageAttachmentSerializer(many=True, read_only=True)
    pending_recipients = serializers.SerializerMethodField()
    delivery_status = serializers.SerializerMethodField()

    class Meta:
        model = Message
        fields = [
            'id', 'conversation', 'sender', 'sender_email', 'sender_public_key',
            'ciphertext', 'content_nonce', 'has_attachments', 'attachments',
            'my_wrap', 'is_read', 'read_at', 'pending_recipients',
            'delivery_status', 'reactions', 'reaction_count',
            'created_at', 'updated_at',
        ]
        read_only_fields = [
            'id', 'sender', 'sender_email', 'sender_public_key', 'my_wrap',
            'attachments', 'is_read', 'read_at', 'pending_recipients',
            'delivery_status', 'created_at', 'updated_at',
        ]

    def _other_participants(self, obj):
        # Read from the prefetched conversation.participants cache and memoize
        # on the message instance — get_pending_recipients and
        # get_delivery_status both call this, so we resolve it once per row
        # with zero extra queries when participants__chat_key is prefetched.
        cached = getattr(obj, '_other_participants_cache', None)
        if cached is None:
            cached = [
                u for u in obj.conversation.participants.all()
                if u.id != obj.sender_id
            ]
            obj._other_participants_cache = cached
        return cached

    def get_pending_recipients(self, obj):
        pending = []
        for u in self._other_participants(obj):
            key = getattr(u, 'chat_key', None)
            if not key or not key.escrow_claimed:
                full = f"{u.first_name} {u.last_name}".strip()
                pending.append(full or (u.email or '').split('@')[0])
        return pending

    def get_delivery_status(self, obj):
        """sent | delivered | read — WhatsApp-style for sender's own messages."""
        if obj.is_read:
            return 'read'
        others = list(self._other_participants(obj))
        if not others:
            return 'sent'
        all_claimed = all(
            getattr(u, 'chat_key', None) and u.chat_key.escrow_claimed for u in others
        )
        return 'delivered' if all_claimed else 'sent'

    def get_sender_public_key(self, obj):
        key = getattr(obj.sender, 'chat_key', None)
        return key.public_key if key else None

    def get_reaction_count(self, obj):
        anno = getattr(obj, 'reaction_count_anno', None)
        if anno is not None:
            return anno
        return obj.reactions.count()

    def get_my_wrap(self, obj):
        request = self.context.get('request')
        if not request or not request.user.is_authenticated:
            return None
        wrap = next(
            (w for w in obj.key_wraps.all() if w.recipient_id == request.user.id),
            None,
        )
        if wrap is None:
            return None
        return {'wrapped_key': wrap.wrapped_key, 'wrap_nonce': wrap.wrap_nonce}


class MessageCreateSerializer(serializers.ModelSerializer):
    """Write serializer — accepts ciphertext + per-recipient wraps."""
    key_wraps = MessageKeyWrapSerializer(many=True, write_only=True)

    class Meta:
        model = Message
        fields = [
            'id', 'conversation', 'ciphertext', 'content_nonce',
            'has_attachments', 'key_wraps',
        ]
        read_only_fields = ['id']

    def validate(self, attrs):
        request = self.context.get('request')
        conversation = attrs.get('conversation')
        if conversation and request:
            if conversation.organization_id != get_active_organization(request).id:
                raise serializers.ValidationError(
                    {'conversation': 'Invalid pk - object does not exist.'}
                )
            if not conversation.participants.filter(id=request.user.id).exists():
                raise serializers.ValidationError('Not a participant of this conversation.')
            if ConversationParticipant.objects.filter(
                conversation=conversation,
            ).exclude(user__organizations=conversation.organization).exists():
                raise serializers.ValidationError(
                    'Conversation contains a participant outside its organization.'
                )
            participant_ids = set(
                str(uid) for uid in conversation.participants.values_list('id', flat=True)
            )
            wrap_ids = {str(w['recipient'].id) for w in attrs.get('key_wraps', [])}
            missing = participant_ids - wrap_ids
            if missing:
                raise serializers.ValidationError(
                    f'Missing key wraps for participants: {sorted(missing)}'
                )
            extra = wrap_ids - participant_ids
            if extra:
                raise serializers.ValidationError(
                    f'Key wraps may only target participants: {sorted(extra)}'
                )
        return attrs

    def create(self, validated_data):
        wraps_data = validated_data.pop('key_wraps', [])
        message = Message.objects.create(**validated_data)
        MessageKeyWrap.objects.bulk_create([
            MessageKeyWrap(message=message, **w) for w in wraps_data
        ])
        return message


class ConversationParticipantSerializer(serializers.ModelSerializer):
    user_email = serializers.EmailField(source='user.email', read_only=True)
    user_name = serializers.SerializerMethodField()
    public_key = serializers.SerializerMethodField()

    class Meta:
        model = ConversationParticipant
        fields = [
            'id', 'user', 'user_email', 'user_name', 'public_key',
            'joined_at', 'last_read_at', 'is_admin',
        ]
        read_only_fields = ['id', 'joined_at']

    def get_user_name(self, obj):
        full = f"{obj.user.first_name} {obj.user.last_name}".strip()
        return full or obj.user.email.split('@')[0]

    def get_public_key(self, obj):
        key = getattr(obj.user, 'chat_key', None)
        return key.public_key if key else None


class ConversationSerializer(serializers.ModelSerializer):
    participants = serializers.SerializerMethodField()
    last_message_at = serializers.SerializerMethodField()
    unread_count = serializers.SerializerMethodField()
    created_by_email = serializers.EmailField(source='created_by.email', read_only=True)

    class Meta:
        model = Conversation
        fields = [
            'id', 'name', 'kind', 'is_group', 'participants', 'created_by',
            'created_by_email', 'last_message_at', 'unread_count',
            'created_at', 'updated_at',
        ]
        read_only_fields = ['id', 'created_by', 'created_at', 'updated_at']

    def get_participants(self, obj):
        participants = obj.conversationparticipant_set.all()
        return ConversationParticipantSerializer(participants, many=True).data

    def get_last_message_at(self, obj):
        # Prefer the queryset annotation (list view) over a per-row query.
        anno = getattr(obj, 'last_message_at_anno', None)
        if anno is not None:
            return anno
        last = obj.messages.order_by('-created_at').first()
        return last.created_at if last else None

    def get_unread_count(self, obj):
        anno = getattr(obj, 'unread_count_anno', None)
        if anno is not None:
            return anno
        request = self.context.get('request')
        if request and request.user.is_authenticated:
            return obj.messages.filter(is_read=False).exclude(sender=request.user).count()
        return 0


class ConversationCreateSerializer(serializers.ModelSerializer):
    participant_ids = serializers.ListField(
        child=serializers.UUIDField(),
        write_only=True,
        required=False,
    )

    class Meta:
        model = Conversation
        fields = ['id', 'name', 'kind', 'is_group', 'participant_ids', 'created_at']
        read_only_fields = ['id', 'is_group', 'created_at']

    def validate_participant_ids(self, value):
        """Every participant must be a member of the caller's active org.

        DRF validates only that the UUIDs are well-formed, so without this an
        arbitrary user id from another tenant could be inserted into a group
        conversation and would then receive its messages (SEC-03).
        """
        from apps.core.models import User
        from apps.core.tenancy import get_active_organization

        if not value:
            return value
        request = self.context.get('request')
        assert request is not None, (
            'ConversationCreateSerializer must receive request in its context '
            'for cross-organization participant validation to run.'
        )
        organization = get_active_organization(request)
        allowed = set(
            User.objects
            .filter(id__in=value, organizations=organization)
            .values_list('id', flat=True)
        )
        # Same shape as an unknown pk: a caller cannot use this to probe
        # whether a user id exists in another organization.
        unknown = [uid for uid in value if uid not in allowed]
        if unknown:
            raise serializers.ValidationError(
                f'Invalid pk "{unknown[0]}" - object does not exist.',
                code='does_not_exist',
            )
        return value

    def create(self, validated_data):
        participant_ids = validated_data.pop('participant_ids', [])
        validated_data['is_group'] = validated_data.get('kind') != Conversation.Kind.DIRECT
        conversation = Conversation.objects.create(**validated_data)
        ConversationParticipant.objects.create(
            conversation=conversation,
            user=conversation.created_by,
            is_admin=True,
        )
        for uid in participant_ids:
            if str(uid) == str(conversation.created_by_id):
                continue
            ConversationParticipant.objects.create(
                conversation=conversation,
                user_id=uid,
            )
        return conversation


class MessageAttachmentUploadSerializer(serializers.ModelSerializer):
    """Multipart upload of an already-encrypted file blob."""
    class Meta:
        model = MessageAttachment
        fields = [
            'id', 'message', 'encrypted_file', 'file_nonce',
            'encrypted_filename', 'filename_nonce', 'mime_hint', 'size',
        ]
        read_only_fields = ['id']

    def validate_message(self, message):
        request = self.context.get('request')
        if request and message.sender_id != request.user.id:
            raise serializers.ValidationError('Only the sender can attach files.')
        return message
