# -*- coding: utf-8 -*-
"""
Extended Accounts Models (Part 2)

This module contains additional models for the accounts application,
including session management, security logging, API keys, and user access.

Models included:
- PermissionGroup: Logical grouping of permissions
- UserSession: Session tracking for security
- UserAccess: External service credentials
- SecurityLog: Security event logging
- APIKey: API access management
- LoginAttempt: Login attempt tracking
- PasswordHistory: Password change history

Author: Adtlas Development Team
Version: 1.0.0
Created: 2024
"""

import uuid
from datetime import timedelta
from typing import Optional, List, Dict, Any

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.core.validators import RegexValidator
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

from apps.core.models import BaseModel, TimeStampedModel
from .models import Permission, Role


class PermissionGroup(BaseModel):
    """
    Logical grouping of permissions for easier management.
    
    This model allows administrators to group related permissions together
    for easier assignment and management. Permission groups can be used
    to create permission templates for common roles.
    
    Attributes:
        name (str): Unique name of the permission group
        display_name (str): Human-readable display name
        description (str): Detailed description of the group
        permissions (ManyToMany): Permissions included in this group
        is_active (bool): Whether the group is currently active
        is_template (bool): Whether this group serves as a template
        color (str): Color code for UI display
        icon (str): Icon identifier for UI display
    """
    
    name = models.CharField(
        max_length=50,
        unique=True,
        validators=[RegexValidator(
            regex=r'^[a-zA-Z0-9_]+$',
            message=_('Group name can only contain letters, numbers, and underscores')
        )],
        verbose_name=_('Group Name'),
        help_text=_('Unique identifier for the permission group')
    )
    
    display_name = models.CharField(
        max_length=100,
        verbose_name=_('Display Name'),
        help_text=_('Human-readable name for the permission group')
    )
    
    description = models.TextField(
        blank=True,
        verbose_name=_('Description'),
        help_text=_('Detailed description of the permission group')
    )
    
    permissions = models.ManyToManyField(
        Permission,
        through='PermissionGroupMembership',
        related_name='groups',
        verbose_name=_('Permissions'),
        help_text=_('Permissions included in this group')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether this permission group is currently active')
    )
    
    is_template = models.BooleanField(
        default=False,
        verbose_name=_('Is Template'),
        help_text=_('Whether this group serves as a template for role creation')
    )
    
    color = models.CharField(
        max_length=7,
        default='#007bff',
        validators=[RegexValidator(
            regex=r'^#[0-9a-fA-F]{6}$',
            message=_('Color must be a valid hex color code')
        )],
        verbose_name=_('Color'),
        help_text=_('Color code for UI display')
    )
    
    icon = models.CharField(
        max_length=50,
        blank=True,
        verbose_name=_('Icon'),
        help_text=_('Icon identifier for UI display')
    )
    
    class Meta:
        db_table = 'permission_groups'
        verbose_name = _('Permission Group')
        verbose_name_plural = _('Permission Groups')
        ordering = ['display_name']
        indexes = [
            models.Index(fields=['name']),
            models.Index(fields=['is_active']),
            models.Index(fields=['is_template']),
        ]
    
    def __str__(self) -> str:
        """
        String representation of the permission group.
        
        Returns:
            str: Display name of the group
        """
        return self.display_name
    
    def get_permission_count(self) -> int:
        """
        Get the number of permissions in this group.
        
        Returns:
            int: Number of permissions in the group
        """
        return self.permissions.filter(is_active=True).count()


class PermissionGroupMembership(TimeStampedModel):
    """
    Through model for PermissionGroup and Permission many-to-many relationship.
    
    This model tracks when permissions are added to or removed from groups,
    providing an audit trail for permission group changes.
    
    Attributes:
        group (PermissionGroup): The permission group
        permission (Permission): The permission
        added_by (User): User who added the permission to the group
        added_at (datetime): When the permission was added
        is_active (bool): Whether the membership is currently active
    """
    
    group = models.ForeignKey(
        PermissionGroup,
        on_delete=models.CASCADE,
        related_name='memberships',
        verbose_name=_('Permission Group')
    )
    
    permission = models.ForeignKey(
        Permission,
        on_delete=models.CASCADE,
        related_name='group_memberships',
        verbose_name=_('Permission')
    )
    
    added_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='permission_group_additions',
        verbose_name=_('Added By')
    )
    
    added_at = models.DateTimeField(
        auto_now_add=True,
        verbose_name=_('Added At')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active')
    )
    
    class Meta:
        db_table = 'permission_group_memberships'
        verbose_name = _('Permission Group Membership')
        verbose_name_plural = _('Permission Group Memberships')
        unique_together = [['group', 'permission']]
        ordering = ['-added_at']
    
    def __str__(self) -> str:
        return f"{self.group.display_name} - {self.permission.name}"


class UserSession(TimeStampedModel):
    """
    Track user sessions for security and analytics.
    
    This model maintains detailed information about user sessions,
    including IP addresses, user agents, and session activity for
    security monitoring and analytics purposes.
    
    Attributes:
        user (User): The user associated with the session
        session_key (str): Django session key
        ip_address (GenericIPAddressField): IP address of the session
        user_agent (str): Browser user agent string
        device_type (str): Type of device (desktop, mobile, tablet)
        browser (str): Browser name and version
        operating_system (str): Operating system information
        location_country (str): Country based on IP geolocation
        location_city (str): City based on IP geolocation
        created_at (datetime): Session creation timestamp
        last_activity (datetime): Last activity timestamp
        expires_at (datetime): Session expiration timestamp
        is_active (bool): Whether the session is currently active
        logout_reason (str): Reason for session termination
    """
    
    # Device type choices
    DEVICE_TYPES = [
        ('desktop', _('Desktop')),
        ('mobile', _('Mobile')),
        ('tablet', _('Tablet')),
        ('unknown', _('Unknown')),
    ]
    
    # Logout reason choices
    LOGOUT_REASONS = [
        ('user', _('User Logout')),
        ('timeout', _('Session Timeout')),
        ('admin', _('Admin Termination')),
        ('security', _('Security Violation')),
        ('system', _('System Shutdown')),
    ]
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='sessions',
        verbose_name=_('User'),
        help_text=_('User associated with this session')
    )
    
    session_key = models.CharField(
        max_length=40,
        unique=True,
        verbose_name=_('Session Key'),
        help_text=_('Django session key')
    )
    
    ip_address = models.GenericIPAddressField(
        verbose_name=_('IP Address'),
        help_text=_('IP address of the session')
    )
    
    user_agent = models.TextField(
        blank=True,
        verbose_name=_('User Agent'),
        help_text=_('Browser user agent string')
    )
    
    device_type = models.CharField(
        max_length=20,
        choices=DEVICE_TYPES,
        default='unknown',
        verbose_name=_('Device Type'),
        help_text=_('Type of device used for the session')
    )
    
    browser = models.CharField(
        max_length=100,
        blank=True,
        verbose_name=_('Browser'),
        help_text=_('Browser name and version')
    )
    
    operating_system = models.CharField(
        max_length=100,
        blank=True,
        verbose_name=_('Operating System'),
        help_text=_('Operating system information')
    )
    
    location_country = models.CharField(
        max_length=100,
        blank=True,
        verbose_name=_('Country'),
        help_text=_('Country based on IP geolocation')
    )
    
    location_city = models.CharField(
        max_length=100,
        blank=True,
        verbose_name=_('City'),
        help_text=_('City based on IP geolocation')
    )
    
    last_activity = models.DateTimeField(
        auto_now=True,
        verbose_name=_('Last Activity'),
        help_text=_('Timestamp of last session activity')
    )
    
    expires_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Expires At'),
        help_text=_('Session expiration timestamp')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether the session is currently active')
    )
    
    logout_reason = models.CharField(
        max_length=20,
        choices=LOGOUT_REASONS,
        blank=True,
        verbose_name=_('Logout Reason'),
        help_text=_('Reason for session termination')
    )
    
    class Meta:
        db_table = 'user_sessions'
        verbose_name = _('User Session')
        verbose_name_plural = _('User Sessions')
        ordering = ['-last_activity']
        indexes = [
            models.Index(fields=['user', 'is_active']),
            models.Index(fields=['session_key']),
            models.Index(fields=['ip_address']),
            models.Index(fields=['last_activity']),
            models.Index(fields=['expires_at']),
        ]
    
    def __str__(self) -> str:
        """
        String representation of the user session.
        
        Returns:
            str: User and IP address information
        """
        return f"{self.user.username} - {self.ip_address}"
    
    @property
    def is_expired(self) -> bool:
        """
        Check if the session has expired.
        
        Returns:
            bool: True if the session has expired
        """
        if self.expires_at:
            return self.expires_at <= timezone.now()
        return False
    
    @property
    def duration(self) -> timedelta:
        """
        Calculate the session duration.
        
        Returns:
            timedelta: Duration of the session
        """
        return self.last_activity - self.created_at
    
    def terminate(self, reason: str = 'user') -> None:
        """
        Terminate the session.
        
        Args:
            reason (str): Reason for termination
        """
        self.is_active = False
        self.logout_reason = reason
        self.save(update_fields=['is_active', 'logout_reason'])


class SecurityLog(TimeStampedModel):
    """
    Security event logging for audit trails and monitoring.
    
    This model logs security-related events throughout the system,
    providing a comprehensive audit trail for security analysis
    and compliance purposes.
    
    Attributes:
        user (User): User associated with the event (optional)
        event_type (str): Type of security event
        severity (str): Severity level of the event
        description (str): Detailed description of the event
        ip_address (GenericIPAddressField): IP address where event occurred
        user_agent (str): User agent string
        request_path (str): URL path of the request
        request_method (str): HTTP method of the request
        session_key (str): Session key if applicable
        additional_data (JSONField): Additional event data
        resolved (bool): Whether the security issue has been resolved
        resolved_by (User): User who resolved the issue
        resolved_at (datetime): When the issue was resolved
    """
    
    # Event type choices
    EVENT_TYPES = [
        ('login_success', _('Successful Login')),
        ('login_failed', _('Failed Login')),
        ('login_blocked', _('Blocked Login Attempt')),
        ('logout', _('User Logout')),
        ('password_change', _('Password Change')),
        ('password_reset', _('Password Reset')),
        ('account_locked', _('Account Locked')),
        ('account_unlocked', _('Account Unlocked')),
        ('permission_denied', _('Permission Denied')),
        ('suspicious_activity', _('Suspicious Activity')),
        ('data_access', _('Sensitive Data Access')),
        ('data_modification', _('Data Modification')),
        ('api_access', _('API Access')),
        ('admin_action', _('Admin Action')),
        ('security_violation', _('Security Violation')),
        ('system_error', _('System Error')),
    ]
    
    # Severity level choices
    SEVERITY_LEVELS = [
        ('low', _('Low')),
        ('medium', _('Medium')),
        ('high', _('High')),
        ('critical', _('Critical')),
    ]
    
    user = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='security_logs',
        verbose_name=_('User'),
        help_text=_('User associated with the security event')
    )
    
    event_type = models.CharField(
        max_length=50,
        choices=EVENT_TYPES,
        verbose_name=_('Event Type'),
        help_text=_('Type of security event')
    )
    
    severity = models.CharField(
        max_length=20,
        choices=SEVERITY_LEVELS,
        default='low',
        verbose_name=_('Severity'),
        help_text=_('Severity level of the event')
    )
    
    description = models.TextField(
        verbose_name=_('Description'),
        help_text=_('Detailed description of the security event')
    )
    
    ip_address = models.GenericIPAddressField(
        null=True,
        blank=True,
        verbose_name=_('IP Address'),
        help_text=_('IP address where the event occurred')
    )
    
    user_agent = models.TextField(
        blank=True,
        verbose_name=_('User Agent'),
        help_text=_('User agent string of the request')
    )
    
    request_path = models.CharField(
        max_length=500,
        blank=True,
        verbose_name=_('Request Path'),
        help_text=_('URL path of the request')
    )
    
    request_method = models.CharField(
        max_length=10,
        blank=True,
        verbose_name=_('Request Method'),
        help_text=_('HTTP method of the request')
    )
    
    session_key = models.CharField(
        max_length=40,
        blank=True,
        verbose_name=_('Session Key'),
        help_text=_('Session key if applicable')
    )
    
    additional_data = models.JSONField(
        default=dict,
        blank=True,
        verbose_name=_('Additional Data'),
        help_text=_('Additional event data in JSON format')
    )
    
    resolved = models.BooleanField(
        default=False,
        verbose_name=_('Resolved'),
        help_text=_('Whether the security issue has been resolved')
    )
    
    resolved_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='resolved_security_logs',
        verbose_name=_('Resolved By'),
        help_text=_('User who resolved the security issue')
    )
    
    resolved_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Resolved At'),
        help_text=_('When the security issue was resolved')
    )
    
    class Meta:
        db_table = 'security_logs'
        verbose_name = _('Security Log')
        verbose_name_plural = _('Security Logs')
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['event_type']),
            models.Index(fields=['severity']),
            models.Index(fields=['user', 'created_at']),
            models.Index(fields=['ip_address']),
            models.Index(fields=['resolved']),
            models.Index(fields=['created_at']),
        ]
    
    def __str__(self) -> str:
        """
        String representation of the security log.
        
        Returns:
            str: Event type and timestamp
        """
        return f"{self.get_event_type_display()} - {self.created_at}"
    
    def resolve(self, resolved_by: User, notes: str = '') -> None:
        """
        Mark the security issue as resolved.
        
        Args:
            resolved_by (User): User resolving the issue
            notes (str): Additional resolution notes
        """
        self.resolved = True
        self.resolved_by = resolved_by
        self.resolved_at = timezone.now()
        
        if notes:
            if 'resolution_notes' not in self.additional_data:
                self.additional_data['resolution_notes'] = []
            self.additional_data['resolution_notes'].append({
                'notes': notes,
                'timestamp': timezone.now().isoformat(),
                'user': resolved_by.username
            })
        
        self.save(update_fields=['resolved', 'resolved_by', 'resolved_at', 'additional_data'])


class APIKey(BaseModel):
    """
    API key management for programmatic access.
    
    This model manages API keys for users and applications,
    providing secure programmatic access to the system with
    proper tracking and access control.
    
    Attributes:
        user (User): User who owns the API key
        name (str): Human-readable name for the API key
        key (str): The actual API key (hashed)
        key_prefix (str): Visible prefix of the key for identification
        permissions (ManyToMany): Permissions granted to this API key
        is_active (bool): Whether the API key is currently active
        expires_at (datetime): When the API key expires
        last_used_at (datetime): When the API key was last used
        usage_count (int): Number of times the key has been used
        rate_limit (int): Rate limit for API requests per hour
        allowed_ips (str): Comma-separated list of allowed IP addresses
        description (str): Description of the API key's purpose
    """
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='api_keys',
        verbose_name=_('User'),
        help_text=_('User who owns this API key')
    )
    
    name = models.CharField(
        max_length=100,
        verbose_name=_('Name'),
        help_text=_('Human-readable name for the API key')
    )
    
    key = models.CharField(
        max_length=255,
        unique=True,
        verbose_name=_('API Key'),
        help_text=_('The actual API key (hashed)')
    )
    
    key_prefix = models.CharField(
        max_length=10,
        verbose_name=_('Key Prefix'),
        help_text=_('Visible prefix of the key for identification')
    )
    
    permissions = models.ManyToManyField(
        Permission,
        blank=True,
        related_name='api_keys',
        verbose_name=_('Permissions'),
        help_text=_('Permissions granted to this API key')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether the API key is currently active')
    )
    
    expires_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Expires At'),
        help_text=_('When the API key expires')
    )
    
    last_used_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Last Used At'),
        help_text=_('When the API key was last used')
    )
    
    usage_count = models.PositiveIntegerField(
        default=0,
        verbose_name=_('Usage Count'),
        help_text=_('Number of times the key has been used')
    )
    
    rate_limit = models.PositiveIntegerField(
        default=1000,
        verbose_name=_('Rate Limit'),
        help_text=_('Rate limit for API requests per hour')
    )
    
    allowed_ips = models.TextField(
        blank=True,
        verbose_name=_('Allowed IPs'),
        help_text=_('Comma-separated list of allowed IP addresses')
    )
    
    description = models.TextField(
        blank=True,
        verbose_name=_('Description'),
        help_text=_('Description of the API key\'s purpose')
    )
    
    class Meta:
        db_table = 'api_keys'
        verbose_name = _('API Key')
        verbose_name_plural = _('API Keys')
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['user', 'is_active']),
            models.Index(fields=['key_prefix']),
            models.Index(fields=['expires_at']),
            models.Index(fields=['last_used_at']),
        ]
    
    def __str__(self) -> str:
        """
        String representation of the API key.
        
        Returns:
            str: API key name and prefix
        """
        return f"{self.name} ({self.key_prefix}...)"
    
    @property
    def is_expired(self) -> bool:
        """
        Check if the API key has expired.
        
        Returns:
            bool: True if the key has expired
        """
        if self.expires_at:
            return self.expires_at <= timezone.now()
        return False
    
    def record_usage(self, ip_address: str = None) -> None:
        """
        Record usage of the API key.
        
        Args:
            ip_address (str): IP address of the request
        """
        self.usage_count += 1
        self.last_used_at = timezone.now()
        self.save(update_fields=['usage_count', 'last_used_at'])
        
        # Log API usage for security monitoring
        SecurityLog.objects.create(
            user=self.user,
            event_type='api_access',
            severity='low',
            description=f'API key "{self.name}" used',
            ip_address=ip_address,
            additional_data={
                'api_key_id': self.id,
                'api_key_name': self.name,
                'usage_count': self.usage_count
            }
        )
    
    def is_ip_allowed(self, ip_address: str) -> bool:
        """
        Check if an IP address is allowed to use this API key.
        
        Args:
            ip_address (str): IP address to check
        
        Returns:
            bool: True if IP is allowed
        """
        if not self.allowed_ips:
            return True  # No restrictions
        
        allowed_list = [ip.strip() for ip in self.allowed_ips.split(',')]
        return ip_address in allowed_list


class UserAccess(BaseModel):
    """
    User access credentials for external services.
    
    This model stores encrypted credentials for external services
    that users need to access as part of their workflow.
    
    Attributes:
        user (User): User who owns the access credentials
        service_name (str): Name of the external service
        service_type (str): Type of service (FTP, VPN, etc.)
        username (str): Username for the external service
        password (str): Encrypted password for the external service
        host (str): Host/server address
        port (int): Port number
        additional_config (JSONField): Additional configuration data
        is_active (bool): Whether the access is currently active
        last_tested_at (datetime): When the access was last tested
        test_status (str): Status of the last test
    """
    
    # Service type choices
    SERVICE_TYPES = [
        ('ftp', _('FTP')),
        ('sftp', _('SFTP')),
        ('ssh', _('SSH')),
        ('vpn', _('VPN')),
        ('database', _('Database')),
        ('api', _('API')),
        ('other', _('Other')),
    ]
    
    # Test status choices
    TEST_STATUSES = [
        ('success', _('Success')),
        ('failed', _('Failed')),
        ('timeout', _('Timeout')),
        ('not_tested', _('Not Tested')),
    ]
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='external_access',
        verbose_name=_('User'),
        help_text=_('User who owns these access credentials')
    )
    
    service_name = models.CharField(
        max_length=100,
        verbose_name=_('Service Name'),
        help_text=_('Name of the external service')
    )
    
    service_type = models.CharField(
        max_length=20,
        choices=SERVICE_TYPES,
        verbose_name=_('Service Type'),
        help_text=_('Type of external service')
    )
    
    username = models.CharField(
        max_length=255,
        verbose_name=_('Username'),
        help_text=_('Username for the external service')
    )
    
    password = models.CharField(
        max_length=255,
        verbose_name=_('Password'),
        help_text=_('Encrypted password for the external service')
    )
    
    host = models.CharField(
        max_length=255,
        blank=True,
        verbose_name=_('Host'),
        help_text=_('Host/server address')
    )
    
    port = models.PositiveIntegerField(
        null=True,
        blank=True,
        verbose_name=_('Port'),
        help_text=_('Port number')
    )
    
    additional_config = models.JSONField(
        default=dict,
        blank=True,
        verbose_name=_('Additional Config'),
        help_text=_('Additional configuration data in JSON format')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether this access is currently active')
    )
    
    last_tested_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Last Tested At'),
        help_text=_('When the access was last tested')
    )
    
    test_status = models.CharField(
        max_length=20,
        choices=TEST_STATUSES,
        default='not_tested',
        verbose_name=_('Test Status'),
        help_text=_('Status of the last connection test')
    )
    
    class Meta:
        db_table = 'user_access'
        verbose_name = _('User Access')
        verbose_name_plural = _('User Access')
        unique_together = [['user', 'service_name']]
        ordering = ['service_name']
        indexes = [
            models.Index(fields=['user', 'service_type']),
            models.Index(fields=['service_type']),
            models.Index(fields=['is_active']),
        ]
    
    def __str__(self) -> str:
        """
        String representation of the user access.
        
        Returns:
            str: Service name and username
        """
        return f"{self.service_name} - {self.username}"
    
    def test_connection(self) -> bool:
        """
        Test the connection to the external service.
        
        Returns:
            bool: True if connection is successful
        
        Note:
            This is a placeholder method. Actual implementation would
            depend on the specific service type and connection requirements.
        """
        # Placeholder implementation
        # In a real implementation, this would test the actual connection
        # based on the service_type
        
        self.last_tested_at = timezone.now()
        
        # Simulate connection test (replace with actual implementation)
        import random
        success = random.choice([True, False])
        
        if success:
            self.test_status = 'success'
        else:
            self.test_status = 'failed'
        
        self.save(update_fields=['last_tested_at', 'test_status'])
        return success


# Signal handlers will be defined in signals.py