"""
User Models for Authentication
This file defines the custom User model using email as the primary identifier.
Passwords are automatically hashed by Django's authentication system.
"""

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
from django.utils import timezone
import random
import string


class UserManager(BaseUserManager):
    """
    Custom user manager where email is the unique identifier
    instead of username.
    """

    def create_user(self, email, password=None, **extra_fields):
        """
        Create and save a regular user with the given email and password.
        """
        if not email:
            raise ValueError('The Email field must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)

        # set_password() hashes the password automatically
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        """
        Create and save a superuser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)
        extra_fields.setdefault('is_email_verified', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self.create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """
    Custom User model that uses email as the username field.
    Passwords are stored in encrypted (hashed) form using Django's default password hasher.
    """

    email = models.EmailField(unique=True, db_index=True)
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150, blank=True, null=True)
    phone_number = models.CharField(max_length=20, blank=True, null=True)

    # Authentication fields
    is_email_verified = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    # Admin role (PRD §5: Moderator / Support admin / Finance admin / Super
    # admin are distinct roles with different restrictions — "Moderator
    # cannot change finance", "Finance admin — no content moderation unless
    # separately granted"). Only meaningful when is_staff=True; irrelevant
    # for regular consumer/business-owner accounts. Defaults to 'super' so
    # every existing staff account keeps its current full access after this
    # migration — narrowing a specific admin down to a scoped role is an
    # explicit action a super admin takes afterward, not a side effect of
    # this field's introduction.
    ADMIN_ROLE_CHOICES = [
        ('super', 'Super Admin'),
        ('moderator', 'Moderator'),
        ('support', 'Support Admin'),
        ('finance', 'Finance Admin'),
    ]
    admin_role = models.CharField(max_length=10, choices=ADMIN_ROLE_CHOICES, default='super')

    # OAuth fields
    is_google_user = models.BooleanField(default=False)
    google_id = models.CharField(max_length=255, blank=True, null=True)
    is_apple_user = models.BooleanField(default=False)
    apple_id = models.CharField(max_length=255, blank=True, null=True)

    # Timestamps
    date_joined = models.DateTimeField(default=timezone.now)
    last_login = models.DateTimeField(blank=True, null=True)

    # Profile fields
    profile_picture = models.ImageField(upload_to='profile_pictures/', blank=True, null=True)
    bio = models.TextField(blank=True, null=True)

    # User interactions
    favorite_businesses = models.ManyToManyField(
        'business.Business',
        related_name='favorited_by',
        blank=True
    )
    following_businesses = models.ManyToManyField(
        'business.Business',
        related_name='followers',
        blank=True
    )

    objects = UserManager()

    # Use email as the unique identifier
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name']  # Required when creating superuser

    class Meta:
        db_table = 'users'
        verbose_name = 'User'
        verbose_name_plural = 'Users'
        ordering = ['-date_joined']

    def __str__(self):
        return self.email

    def get_full_name(self):
        """Return the user's full name."""
        if self.last_name:
            return f"{self.first_name} {self.last_name}"
        return self.first_name

    def get_short_name(self):
        """Return the user's first name."""
        return self.first_name


class EmailVerificationOTP(models.Model):
    """
    Model to store OTP for email verification during registration.
    OTP expires after a certain time period (default: 10 minutes).
    """

    email = models.EmailField(db_index=True)
    otp = models.CharField(max_length=6)
    created_at = models.DateTimeField(auto_now_add=True)
    is_verified = models.BooleanField(default=False)

    # Store temporary user data until email is verified
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150, blank=True, null=True)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    password_hash = models.CharField(max_length=255)  # Store hashed password

    class Meta:
        db_table = 'email_verification_otps'
        ordering = ['-created_at']

    def __str__(self):
        return f"OTP for {self.email}"

    @staticmethod
    def generate_otp():
        """Generate a random 6-digit OTP."""
        return ''.join(random.choices(string.digits, k=6))

    def is_expired(self):
        """Check if OTP is expired (10 minutes by default)."""
        from django.conf import settings
        from datetime import timedelta

        expiry_time = getattr(settings, 'OTP_EXPIRY_MINUTES', 10)
        return timezone.now() > self.created_at + timedelta(minutes=expiry_time)


class PasswordResetToken(models.Model):
    """
    Model to store temporary passwords for password reset.
    Token expires after a certain time period.
    """

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    temp_password = models.CharField(max_length=12)
    created_at = models.DateTimeField(auto_now_add=True)
    is_used = models.BooleanField(default=False)

    class Meta:
        db_table = 'password_reset_tokens'
        ordering = ['-created_at']

    def __str__(self):
        return f"Password reset for {self.user.email}"

    @staticmethod
    def generate_temp_password():
        """Generate a random temporary password."""
        # Generate 12-character password with letters and digits
        return ''.join(random.choices(string.ascii_letters + string.digits, k=12))

    def is_expired(self):
        """Check if token is expired (30 minutes by default)."""
        from datetime import timedelta
        return timezone.now() > self.created_at + timedelta(minutes=30)


class ImpersonationSession(models.Model):
    """
    A short-lived, single-use exchange code letting a Support admin open a
    real session as another user (PRD §5: "impersonation with audit trail").
    The code itself carries no session power on its own — it's traded for
    real JWT tokens exactly once, within a short window, via
    POST /api/auth/impersonate/exchange/ — so a code that ends up in browser
    history or a server log can't be replayed later.
    """
    admin = models.ForeignKey(User, on_delete=models.CASCADE, related_name='impersonation_sessions_started')
    target_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='impersonation_sessions_received')
    code = models.CharField(max_length=64, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    exchanged_at = models.DateTimeField(null=True, blank=True)
    ended_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        db_table = 'impersonation_sessions'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.admin.email} as {self.target_user.email}"

    @staticmethod
    def generate_code():
        import secrets
        return secrets.token_urlsafe(32)

    def is_expired(self):
        from datetime import timedelta
        return timezone.now() > self.created_at + timedelta(seconds=60)


class DeviceToken(models.Model):
    """
    Model to store FCM device tokens for push notifications.
    Supports multiple devices per user.
    """
    DEVICE_TYPES = [
        ('android', 'Android'),
        ('ios', 'iOS'),
        ('web', 'Web'),
    ]

    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='device_tokens')
    token = models.CharField(max_length=500, unique=True)
    device_type = models.CharField(max_length=10, choices=DEVICE_TYPES, default='android')
    device_name = models.CharField(max_length=100, blank=True, null=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'device_tokens'
        ordering = ['-updated_at']

    def __str__(self):
        return f"Device token for {self.user.email} ({self.device_type})"
