# -*- coding: utf-8 -*-
"""
Adtlas Accounts Models

This module contains the custom user authentication models for the Adtlas DAI Management System.
It implements a flexible role-based permission system with dynamic roles and comprehensive
user management capabilities.

Features:
    - Custom User model with email-based authentication
    - Dynamic Role system with hierarchical permissions
    - User profiles with detailed information
    - Activity tracking and session management
    - Department-based organization structure

Author: Adtlas Development Team
Version: 2.0.0
Last Updated: 2025-07-08
"""

import uuid 
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.core.validators import RegexValidator
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import AbstractUser, PermissionsMixin, Permission

from apps.accounts.managers import UserManager
from apps.common.models import BaseModel, TimeStampedModel


class Department(BaseModel):
    """
    Department model for organizing users within the company structure.
    Departments can have hierarchical relationships and specific permissions.
    """

    name = models.CharField(
        max_length=100,
        unique=True,
        help_text="Department name (e.g., 'Marketing', 'Sales', 'IT')"
    )

    code = models.CharField(
        max_length=20,
        unique=True,
        validators=[RegexValidator(r'^[A-Z]{2,20}$', "Code must be uppercase letters only")],
        help_text="Department code (e.g., 'MKT', 'SALES', 'IT')"
    )

    description = models.TextField(
        blank=True,
        help_text="Detailed description of the department's responsibilities"
    )

    parent = models.ForeignKey(
        "self",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="children",
        help_text="Parent department for hierarchical structure"
    )

    head = models.ForeignKey(
        "accounts.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="headed_departments",
        help_text="Department head/manager"
    )

    is_active = models.BooleanField(
        default=True,
        help_text="Whether this department is currently active"
    )

    class Meta:
        db_table = "accounts_departments"
        ordering = ["name"]
        verbose_name = "Department"
        verbose_name_plural = "Departments"

    def __str__(self):
        return f"{self.name} ({self.code})"

    def get_all_children(self):
        """Get all child departments recursively"""
        children = list(self.children.all())
        for child in self.children.all():
            children.extend(child.get_all_children())
        return children

    def get_all_users(self):
        """Get all users in this department and its children"""
        from django.db.models import Q
        departments = [self] + self.get_all_children()
        return User.objects.filter(profile__department__in=departments)


class Role(BaseModel):
    """
    Role model for dynamic role management.
    
    This model allows creating custom roles with specific permissions
    that can be assigned to users for flexible access control.
    """

    ROLE_TYPES = [
        ("system", "System Role"),
        ("custom", "Custom Role"),
        ("department", "Department Role"),
        ("project", "Project Role"),
    ]

    # Role identification fields
    name = models.CharField(
        max_length=100, 
        unique=True,
        verbose_name=_("Role Name"),
        help_text=_("Unique name for the role (e.g., 'Campaign Manager', 'Analytics Viewer')")
    )
    
    # 
    code = models.CharField(
        max_length=50,
        unique=True,
        validators=[RegexValidator(r"^[a-z_]+$", "Code must be lowercase with underscores only")],
        help_text="Role code for programmatic access (e.g., 'campaign_manager')"
    )

    # Role description for better understanding
    description = models.TextField(
        blank=True,
        verbose_name=_("Description"),
        help_text=_("Detailed description of the role's responsibilities and permissions")
    )
 
    role_type = models.CharField(
        max_length=20,
        choices=ROLE_TYPES,
        default="custom",
        help_text="Type of role for categorization"
    )

    parent_role = models.ForeignKey(
        "self",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="child_roles",
        help_text="Parent role for inheritance"
    )

    # Role permissions - many-to-many relationship with Django"s Permission model
    permissions = models.ManyToManyField(
        Permission,
        blank=True,
        verbose_name=_("Permissions"),
        help_text=_("Specific permissions assigned to this role")
    )

    # Role status - whether the role is active or not
    is_active = models.BooleanField(
        default=True,
        verbose_name=_("Is Active"),
        help_text=_("Whether this role is currently active")
    )

    # Role priority for hierarchical access control
    level = models.PositiveIntegerField(
        default=1,
        verbose_name=_("Priority"),
        help_text="Role hierarchy level (1=highest, 5=lowest)"
    )
 
    is_default = models.BooleanField(
        default=False,
        help_text="Whether this is a default role for new users"
    )

    class Meta:
        verbose_name = _("Role")
        verbose_name_plural = _("Roles")
        db_table = "accounts_roles"
        ordering = ["level", "name"] 

    def __str__(self):
        """String representation of the role."""
        return f"{self.name} (Level {self.level})"

    def get_all_permissions(self):
        """Get all permissions including inherited from parent roles"""
        permissions = set(self.permissions.all())
        if self.parent_role:
            permissions.update(self.parent_role.get_all_permissions())
        return permissions

    def has_permission(self, permission_codename, app_label=None):
        """Check if role has specific permission"""
        permissions = self.get_all_permissions()
        for perm in permissions:
            if app_label:
                if perm.codename == permission_codename and perm.content_type.app_label == app_label:
                    return True
            else:
                if perm.codename == permission_codename:
                    return True
        return False


class User(AbstractUser, PermissionsMixin):
    """
    Custom User model with email-based authentication and enhanced features.
    
    This model replaces Django"s default User model and provides:
    - Email-based authentication
    - Enhanced profile information
    - Role-based access control
    - Activity tracking
    - Multi-factor authentication support
    """
    
    # Core fields 
    # Enhanced email field - required and unique
    email = models.EmailField(
        _("Email Address"),
        unique=True,
        help_text="User's email address (used for login)"
    )

    username = models.CharField(
        max_length=150,
        unique=True,
        null=True,
        blank=True,
        help_text="Optional username for display purposes"
    )

    first_name = models.CharField(
        max_length=150,
        blank=True,
        help_text="User's first name"
    )

    last_name = models.CharField(
        max_length=150,
        blank=True,
        help_text="User's last name"
    )
    
    # Status fields
    is_active = models.BooleanField(
        default=True,
        help_text="Designates whether this user should be treated as active"
    )
    is_staff = models.BooleanField(
        default=False,
        help_text="Designates whether the user can log into the admin site"
    )

    # Account status fields
    is_verified = models.BooleanField(
        default=False,
        verbose_name=_("Is Verified"),
        help_text=_("Whether the user's email is verified")
    )

    # Account activity tracking
    last_login_ip = models.GenericIPAddressField(
        null=True,
        blank=True,
        verbose_name=_("Last Login IP"),
        help_text=_("IP address of last login")
    )

    # Account dates
    email_verified_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_("Email Verified At"),
        help_text=_("When the email was verified")
    )

    # Timestamps
    date_joined = models.DateTimeField(
        default=timezone.now,
        help_text="Date when the user account was created"
    )
    last_login = models.DateTimeField(
        null=True,
        blank=True,
        help_text="Last time the user logged in"
    )
    last_activity = models.DateTimeField(
        null=True,
        blank=True,
        help_text="Last time the user was active in the system"
    )
    
    # Security fields
    failed_login_attempts = models.PositiveIntegerField(
        default=0,
        help_text="Number of consecutive failed login attempts"
    )
    account_locked_until = models.DateTimeField(
        null=True,
        blank=True,
        help_text="Account lockout expiration time"
    )
    password_changed_at = models.DateTimeField(
        default=timezone.now,
        help_text="When the password was last changed"
    )
    mfa_enabled = models.BooleanField(
        default=False,
        help_text="Whether multi-factor authentication is enabled"
    )
    
    objects = UserManager()

    # Override username field to use email for authentication
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username", "first_name", "last_name"]

    class Meta:
        verbose_name = _("User")
        verbose_name_plural = _("Users")
        db_table = "accounts_users"
        ordering = ["-date_joined"]
        indexes = [
            models.Index(fields=["email"]),
            models.Index(fields=["is_active", "is_verified"]),
            models.Index(fields=["date_joined"]),
        ]

    def __str__(self):
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name} ({self.email})"
        return self.email
 
    def get_full_name(self):
        """Get user"s full name."""
        return f"{self.first_name} {self.last_name}".strip() or self.username

    def get_short_name(self):
        """Get user"s short name."""
        return self.first_name or self.username

    def is_account_locked(self):
        """Check if the account is currently locked."""
        if self.account_locked_until:
            return timezone.now() < self.account_locked_until
        return False

    def unlock_account(self):
        """Unlock the user account."""
        self.account_locked_until = None
        self.failed_login_attempts = 0
        self.save(update_fields=["account_locked_until", "failed_login_attempts"])

    def get_roles(self):
        """Get all active roles assigned to this user."""
        return Role.objects.filter(
            userrole__user=self,
            userrole__is_active=True,
            is_active=True
        )

    def has_role(self, role_code):
        """Check if user has a specific role."""
        return self.get_roles().filter(code=role_code).exists()

    def get_highest_role(self):
        """Get the user"s highest priority role."""
        return self.get_roles().order_by("level").first()

    def can_access_app(self, app_label):
        """Check if user can access a specific app."""
        roles = self.get_roles()
        for role in roles:
            if role.has_permission("view", app_label):
                return True
        return False

    def verify_email(self):
        """Mark user"s email as verified."""
        self.is_verified = True
        self.email_verified_at = timezone.now()
        self.save(update_fields=["is_verified", "email_verified_at"])
    
    def update_last_login_ip(self, ip_address):
        """Update user"s last login IP address."""
        self.last_login_ip = ip_address
        self.save(update_fields=["last_login_ip"])

    def clean(self):
        """Validate the model data."""
        super().clean()
        
        # Ensure email is lowercase
        if self.email:
            self.email = self.email.lower()
    
    def save(self, *args, **kwargs):
        """Override save to add custom logic."""
        self.full_clean()
        super().save(*args, **kwargs)
    
    def get_absolute_url(self):
        """Return the absolute URL for the user profile."""
        return reverse("accounts:profile", kwargs={"pk": self.pk})

        
class Profile(BaseModel): 
    """
    Extended user profile model for additional user information.
    
    This model stores additional user data that doesn"t belong
    in the core User model, following the principle of separation of concerns.
    """
     
    # One-to-one relationship with User model
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
        related_name="profile",
        verbose_name=_("User"),
        help_text=_("Associated user account")
    )
    
    # Personal Information
    # Phone number with validation
    phone_regex = RegexValidator(
        regex=r"^\+?1?\d{9,15}$",
        message=_("Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
    )
    phone_number = models.CharField(
        _("Phone Number"),
        validators=[phone_regex],
        max_length=17,
        blank=True, 
        null=True,
        help_text=_("User's phone number")
    )

    # Profile image
    avatar = models.ImageField(
        _("Avatar"),
        upload_to="avatars/%Y/%m/",
        blank=True,
        null=True, 
        help_text=_("User's profile picture")
    )

    # Personal information
    bio = models.TextField(
        _("Biography"),
        max_length=500,
        blank=True, 
        help_text=_("User's biography or description")
    )

    # Birth date
    birth_date = models.DateField(
        null=True,
        blank=True,
        verbose_name=_("Birth Date"),
        help_text=_("User's birth date")
    )
    
    # Professional information
    job_title = models.CharField(
        _("Job Title"),
        max_length=100,
        blank=True,
        help_text=_("User job title or position")
    )

    company = models.CharField(
        _("Company"),
        max_length=100,
        blank=True,
        help_text=_("User company or organization")
    )

    # Website URL
    website = models.URLField(
        blank=True,
        verbose_name=_("Website"),
        help_text=_("User's website URL")
    )
    
    department = models.ForeignKey(
        Department,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="users",
        help_text="User department within the company"
    )

    employee_id = models.CharField(
        max_length=50,
        blank=True,
        unique=True,
        null=True,
        help_text="Employee ID or staff number"
    )

    hire_date = models.DateField(
        null=True,
        blank=True,
        help_text="Date when the user was hired"
    )

    manager = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="subordinates",
        help_text="User's direct manager"
    )
    
    # Preferences
    timezone = models.CharField(
        max_length=50,
        default="UTC",
        help_text="User's preferred timezone"
    )

    language = models.CharField(
        max_length=10,
        default="en",
        help_text="User's preferred language"
    )

    theme = models.CharField(
        max_length=20,
        choices=[
            ("light", "Light Theme"),
            ("dark", "Dark Theme"),
            ("auto", "Auto (System)"),
        ],
        default="light",
        help_text="User's preferred UI theme"
    )

    notifications_enabled = models.BooleanField(
        default=True,
        help_text="Whether to receive notifications"
    )

    sms_notifications = models.BooleanField(
        default=False,
        verbose_name=_("SMS Notifications"),
        help_text=_("Receive SMS notifications")
    )

    # Notification preferences
    email_notifications = models.BooleanField(
        default=True,
        verbose_name=_("Email Notifications"),
        help_text=_("Receive email notifications")
    )
    
    # Contact Information
    # Address information
    address_line1 = models.CharField(
        _("Address Line 1"),
        max_length=255,
        blank=True,
        help_text=_("Street address")
    )
    
    address_line2 = models.CharField(
        _("Address Line 2"),
        max_length=255,
        blank=True,
        help_text=_("Apartment, suite, etc.")
    )
    
    city = models.CharField(
        _("City"),
        max_length=100,
        blank=True,
        help_text=_("City")
    )
    
    state = models.CharField(
        _("State/Province"),
        max_length=100,
        blank=True,
        help_text=_("State or province")
    )
    
    postal_code = models.CharField(
        _("Postal Code"),
        max_length=20,
        blank=True,
        help_text=_("ZIP or postal code")
    )
    
    country = models.CharField(
        _("Country"),
        max_length=100,
        blank=True,
        help_text=_("Country")
    )

    emergency_contact_name = models.CharField(
        max_length=100,
        blank=True,
        help_text="Emergency contact person name"
    )

    emergency_contact_phone = models.CharField(
        max_length=20,
        blank=True,
        help_text="Emergency contact phone number"
    )

    class Meta:
        verbose_name = _("Profile")
        verbose_name_plural = _("Profiles")
        ordering = ["-created_at"]
        db_table = "accounts_profile" 

    def __str__(self):
        """String representation of the user profile."""
        return f"Profile for {self.user.get_full_name() or self.user.email}"

    def get_age(self):
        """Calculate user"s age based on date of birth."""
        if self.date_of_birth:
            today = timezone.now().date()
            return today.year - self.date_of_birth.year - (
                (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
            )
        return None

    def get_avatar_url(self):
        """Get avatar URL or default avatar."""
        if self.avatar:
            return self.avatar.url
        return "/static/images/default-avatar.png"

    def get_absolute_url(self):
        """Return the absolute URL for the user profile."""
        return reverse("accounts:profile", kwargs={"pk": self.user.pk})


class UserRole(BaseModel):
    """
    Junction model for User-Role relationships with temporal aspects.
    Allows users to have multiple roles with validity periods.
    """
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        help_text="User assigned to this role"
    )
    role = models.ForeignKey(
        Role,
        on_delete=models.CASCADE,
        help_text="Role assigned to the user"
    )
    assigned_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="assigned_roles",
        help_text="User who assigned this role"
    )
    assigned_at = models.DateTimeField(
        default=timezone.now,
        help_text="When this role was assigned"
    )
    valid_from = models.DateTimeField(
        default=timezone.now,
        help_text="When this role assignment becomes valid"
    )
    valid_until = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this role assignment expires"
    )
    is_active = models.BooleanField(
        default=True,
        help_text="Whether this role assignment is currently active"
    )
    notes = models.TextField(
        blank=True,
        help_text="Additional notes about this role assignment"
    )

    class Meta:
        db_table = "accounts_user_roles"
        unique_together = ["user", "role"]
        verbose_name = "User Role"
        verbose_name_plural = "User Roles"
        ordering = ["-assigned_at"]

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

    def is_valid(self):
        """Check if the role assignment is currently valid."""
        now = timezone.now()
        return (
            self.is_active and
            self.valid_from <= now and
            (self.valid_until is None or self.valid_until > now)
        )


class UserSession(BaseModel):
    """
    Track user sessions for security and analytics purposes.
    """
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name="sessions",
        help_text="User associated with this session"
    )
    session_key = models.CharField(
        max_length=40,
        unique=True,
        help_text="Django session key"
    )
    ip_address = models.GenericIPAddressField(
        null=True,
        blank=True,
        help_text="IP address of the session"
    )
    user_agent = models.TextField(
        blank=True,
        help_text="Browser user agent string"
    )
    location = models.CharField(
        max_length=200,
        blank=True,
        help_text="Geographic location (if available)"
    )
    is_active = models.BooleanField(
        default=True,
        help_text="Whether the session is currently active"
    )
    last_activity = models.DateTimeField(
        default=timezone.now,
        help_text="Last activity timestamp"
    )
    expires_at = models.DateTimeField(
        help_text="When the session expires"
    )

    class Meta:
        db_table = "accounts_user_sessions"
        verbose_name = "User Session"
        verbose_name_plural = "User Sessions"
        ordering = ["-last_activity"]

    def __str__(self):
        return f"{self.user.email} - {self.session_key[:8]}..."

    def is_expired(self):
        """Check if the session has expired."""
        return timezone.now() > self.expires_at


class UserActivity(TimeStampedModel):
    """
    Track user activities for audit and analytics purposes.
    """
    
    ACTION_TYPES = [
        ("login", "Login"),
        ("logout", "Logout"),
        ("create", "Create"),
        ("read", "View"),
        ("update", "Update"),
        ("delete", "Delete"),
        ("export", "Export"),
        ("import", "Import"),
        ("admin", "Admin Action"),
    ]
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name="activities",
        help_text="User who performed the action"
    )
    action = models.CharField(
        max_length=20,
        choices=ACTION_TYPES,
        help_text="Type of action performed"
    )
    object_type = models.CharField(
        max_length=50,
        blank=True,
        help_text="Type of object affected (model name)"
    )
    object_id = models.CharField(
        max_length=50,
        blank=True,
        help_text="ID of the affected object"
    )
    object_repr = models.CharField(
        max_length=200,
        blank=True,
        help_text="String representation of the affected object"
    )
    details = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional details about the action"
    )
    ip_address = models.GenericIPAddressField(
        null=True,
        blank=True,
        help_text="IP address from which the action was performed"
    )
    user_agent = models.TextField(
        blank=True,
        help_text="Browser user agent string"
    )

    class Meta:
        db_table = "accounts_user_activities"
        verbose_name = "User Activity"
        verbose_name_plural = "User Activities"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["user", "action"]),
            models.Index(fields=["created_at"]),
            models.Index(fields=["object_type", "object_id"]),
        ]

    def __str__(self):
        return f"{self.user.email} - {self.action} - {self.created_at}"


class PasswordResetToken(BaseModel):
    """
    Secure password reset tokens with expiration.
    """
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name="reset_tokens",
        help_text="User requesting password reset"
    )
    token = models.UUIDField(
        default=uuid.uuid4,
        unique=True,
        help_text="Unique reset token"
    )
    expires_at = models.DateTimeField(
        help_text="When the token expires"
    )
    is_used = models.BooleanField(
        default=False,
        help_text="Whether the token has been used"
    )
    ip_address = models.GenericIPAddressField(
        null=True,
        blank=True,
        help_text="IP address from which reset was requested"
    )

    class Meta:
        db_table = "accounts_password_reset_tokens"
        verbose_name = "Password Reset Token"
        verbose_name_plural = "Password Reset Tokens"
        ordering = ["-created_at"]

    def __str__(self):
        return f"Reset token for {self.user.email}"

    def is_expired(self):
        """Check if the token has expired."""
        return timezone.now() > self.expires_at

    def is_valid(self):
        """Check if the token is valid for use."""
        return not self.is_used and not self.is_expired()
