import secrets
import string
import time
import uuid
from functools import cached_property

from django.db import models
from django.contrib.auth.models import AbstractUser, UserManager
from django.utils import timezone


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)


def generate_username() -> str:
    """Generate random 7-character alphanumeric username."""
    chars = string.ascii_uppercase + string.digits
    return ''.join(secrets.choice(chars) for _ in range(7))


class UserManager(UserManager):
    """Custom manager for email-based authentication."""

    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("Email is required")
        email = self.normalize_email(email)
        extra_fields.setdefault("username", generate_username())
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        return self.create_user(email, password, **extra_fields)


class User(AbstractUser):
    """Extended user model with avatar and metadata."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    email = models.EmailField(unique=True)
    avatar = models.ImageField(upload_to="avatars/", blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True)
    bio = models.TextField(blank=True)
    must_change_password = models.BooleanField(default=False)
    onboarding_completed = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []
    objects = UserManager()

    def save(self, *args, **kwargs):
        if not self.username:
            while True:
                username = generate_username()
                if not User.objects.filter(username=username).exists():
                    self.username = username
                    break
        super().save(*args, **kwargs)

    class Meta:
        db_table = "users"

    RBAC_CACHE_TTL = 60  # seconds; role changes also invalidate explicitly

    @staticmethod
    def rbac_cache_key(user_id) -> str:
        return f"rbac:perms:{user_id}"

    @cached_property
    def role_permission_codenames(self) -> set[str]:
        """Flattened set of `<domain>.<action>` codenames from all assigned roles.

        Superuser short-circuits to {"*"}. Cached per instance (one request)
        and in the Django cache across requests (60s TTL, invalidated on role
        changes — see rbac_views). With LocMemCache other processes may stay
        stale up to the TTL.
        """
        if self.is_superuser:
            return {"*"}
        from django.core.cache import cache

        key = self.rbac_cache_key(self.pk)
        cached = cache.get(key)
        if cached is not None:
            return cached
        pairs = Permission.objects.filter(
            roles__user_assignments__user=self
        ).values_list("domain", "action").distinct()
        perms = {f"{d}.{a}" for d, a in pairs}
        cache.set(key, perms, self.RBAC_CACHE_TTL)
        return perms

    def has_menu_perm(self, codename: str) -> bool:
        perms = self.role_permission_codenames
        if "*" in perms or codename in perms:
            return True
        domain = codename.split(".", 1)[0]
        return f"{domain}.*" in perms


class Permission(models.Model):
    """Menu-domain permission. Codename = `<domain>.<action>`."""
    domain = models.CharField(max_length=40)
    action = models.CharField(max_length=20)
    label = models.CharField(max_length=120)

    class Meta:
        db_table = "rbac_permissions"
        unique_together = [("domain", "action")]
        ordering = ["domain", "action"]

    def __str__(self):
        return f"{self.domain}.{self.action}"

    @property
    def codename(self) -> str:
        return f"{self.domain}.{self.action}"


class Role(models.Model):
    """Named bundle of permissions, assignable to users."""
    name = models.CharField(max_length=80, unique=True)
    slug = models.SlugField(max_length=80, unique=True)
    description = models.TextField(blank=True)
    is_system = models.BooleanField(default=False)
    permissions = models.ManyToManyField(Permission, related_name="roles", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "rbac_roles"
        ordering = ["name"]

    def __str__(self):
        return self.name


class UserRole(models.Model):
    """User <-> Role assignment with audit fields."""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_roles")
    role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="user_assignments")
    assigned_at = models.DateTimeField(auto_now_add=True)
    assigned_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
    )

    class Meta:
        db_table = "rbac_user_roles"
        unique_together = [("user", "role")]
        ordering = ["-assigned_at"]

    def __str__(self):
        return f"{self.user.email} -> {self.role.name}"


class Tag(models.Model):
    """Tags for organizing and filtering items."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    slug = models.SlugField(max_length=50, unique=True, blank=True, null=True)
    name = models.CharField(max_length=50, unique=True)
    color = models.CharField(max_length=7, default="#6b7280")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "tags"
        ordering = ["name"]

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug and self.name:
            self.slug = self.name.lower().replace(" ", "-")
        super().save(*args, **kwargs)


class Attachment(models.Model):
    """File attachments for various entities."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    file = models.FileField(upload_to="attachments/%Y/%m/")
    name = models.CharField(max_length=255)
    size = models.IntegerField()
    mime_type = models.CharField(max_length=100)
    uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="attachments")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "attachments"
        ordering = ["-created_at"]


class Comment(models.Model):
    """Comments on tasks, projects, events, etc."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "comments"
        ordering = ["created_at"]


class Notification(models.Model):
    """User notifications."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications")
    title = models.CharField(max_length=255)
    message = models.TextField()
    is_read = models.BooleanField(default=False)
    link = models.CharField(max_length=500, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "notifications"
        ordering = ["-created_at"]


class AuditLog(models.Model):
    """Audit trail for tracking changes."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    action = models.CharField(max_length=50)
    model_name = models.CharField(max_length=100)
    object_id = models.UUIDField()
    changes = models.JSONField(default=dict)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "audit_logs"
        ordering = ["-created_at"]


class AppConfig(models.Model):
    """Singleton system configuration — AI providers and outgoing email.

    Stores API keys in the database so admins can manage them from Settings
    instead of editing env files. Falls back to env vars when a field is blank.
    """
    AI_PROVIDERS = [
        ("deepseek", "DeepSeek"),
        ("minimax", "MiniMax M3"),
    ]

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)

    # --- AI assistant ---
    ai_provider = models.CharField(max_length=20, choices=AI_PROVIDERS, default="deepseek")

    deepseek_api_key = models.CharField(max_length=255, blank=True)
    deepseek_base_url = models.CharField(max_length=255, blank=True, default="https://api.deepseek.com")
    deepseek_model = models.CharField(max_length=100, blank=True, default="deepseek-chat")

    minimax_api_key = models.CharField(max_length=255, blank=True)
    minimax_base_url = models.CharField(max_length=255, blank=True, default="https://api.minimax.io/v1")
    minimax_model = models.CharField(max_length=100, blank=True, default="MiniMax-M3")

    # --- Outgoing email (SMTP) ---
    email_host = models.CharField(max_length=255, blank=True)
    email_port = models.PositiveIntegerField(default=587)
    email_username = models.CharField(max_length=255, blank=True)
    email_password = models.CharField(max_length=255, blank=True)
    email_from = models.EmailField(blank=True)
    email_use_tls = models.BooleanField(default=True)

    # --- Calendar ---
    calendar_default_view = models.CharField(
        max_length=10,
        choices=[("week", "Week"), ("month", "Month")],
        default="week",
    )

    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "app_config"

    def __str__(self):
        return "App Configuration"