import uuid
import secrets
import time

from django.db import models
from django.conf import settings


def uuid7() -> uuid.UUID:
    """Generate a UUID v7 (time-ordered UUID)."""
    nanoseconds = time.time_ns()
    uuid_int = (nanoseconds << 16) | secrets.randbits(48)
    return uuid.UUID(int=uuid_int)


class UserKey(models.Model):
    """X25519 public key per user for E2E chat encryption.

    Steady state: only public_key is stored; private key lives in the user's
    browser IndexedDB and never touches the server.

    Bootstrap state: when a user is auto-provisioned (signal on user create
    or backfill command), the server briefly holds the matching private key
    in `private_key_escrow` so the client can claim it on first login. This
    is a deliberate, documented compromise — server CAN read messages sent
    to a user until that user first logs in and claims/deletes the escrow.
    Once `escrow_claimed=True`, the escrow column is cleared and the key
    is fully E2E from that point.
    """
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='chat_key',
    )
    public_key = models.CharField(max_length=128)
    private_key_escrow = models.TextField(blank=True, default='')
    escrow_claimed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    rotated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'chat_user_keys'

    def __str__(self):
        return f"UserKey({self.user_id})"


class Conversation(models.Model):
    """Chat conversation between users."""
    class Kind(models.TextChoices):
        DIRECT = 'direct', 'Direct'
        GROUP = 'group', 'Group'
        CHANNEL = 'channel', 'Channel'

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    name = models.CharField(max_length=255, blank=True)
    kind = models.CharField(max_length=16, choices=Kind.choices, default=Kind.DIRECT)
    is_group = models.BooleanField(default=False)
    participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through='ConversationParticipant',
        related_name='conversations'
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='created_conversations',
        null=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'chat_conversations'
        ordering = ['-updated_at']

    def __str__(self):
        return self.name or f"Conversation {self.id}"


class ConversationParticipant(models.Model):
    """Through model for conversation participants."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    joined_at = models.DateTimeField(auto_now_add=True)
    last_read_at = models.DateTimeField(null=True, blank=True)
    is_admin = models.BooleanField(default=False)

    class Meta:
        db_table = 'chat_conversation_participants'
        unique_together = ['conversation', 'user']


class Message(models.Model):
    """Chat message — stored as ciphertext only.

    Sender encrypts plaintext once with a random session key K (XSalsa20-Poly1305),
    then wraps K for each recipient using authenticated crypto_box
    (sender_priv + recipient_pub). Server never sees plaintext or K.
    """
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    conversation = models.ForeignKey(
        Conversation,
        on_delete=models.CASCADE,
        related_name='messages'
    )
    sender = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='sent_messages'
    )
    ciphertext = models.TextField(blank=True, default='')
    content_nonce = models.CharField(max_length=64, blank=True, default='')
    has_attachments = models.BooleanField(default=False)
    is_read = models.BooleanField(default=False)
    read_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'chat_messages'
        ordering = ['created_at']
        indexes = [
            models.Index(fields=['is_read']),
            models.Index(fields=['conversation', 'created_at']),
        ]

    def __str__(self):
        return f"Message({self.id})"


class MessageKeyWrap(models.Model):
    """Per-recipient wrap of the session key K used to encrypt a Message.

    wrapped_key = crypto_box(K, wrap_nonce, recipient_pub, sender_priv).
    Recipient decrypts with crypto_box_open using sender_pub + recipient_priv.
    """
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='key_wraps')
    recipient = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='+')
    wrapped_key = models.TextField()
    wrap_nonce = models.CharField(max_length=64)

    class Meta:
        db_table = 'chat_message_key_wraps'
        unique_together = ['message', 'recipient']
        indexes = [models.Index(fields=['recipient', 'message'])]


class MessageAttachment(models.Model):
    """Encrypted file attachment for a Message.

    File bytes encrypted client-side with the same session key K used for the
    message body (secretbox with file_nonce). Server stores ciphertext blob only.
    Filename is encrypted separately (secretbox, filename_nonce) so server
    never sees it.
    """
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='attachments')
    encrypted_file = models.FileField(upload_to='chat/attachments/%Y/%m/')
    file_nonce = models.CharField(max_length=64)
    encrypted_filename = models.TextField()
    filename_nonce = models.CharField(max_length=64)
    mime_hint = models.CharField(max_length=128, blank=True, default='application/octet-stream')
    size = models.BigIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'chat_message_attachments'
        ordering = ['created_at']


class MessageReaction(models.Model):
    """Reaction to a message."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='reactions')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    reaction = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'chat_message_reactions'
        unique_together = ['message', 'user', 'reaction']
