"""
Modèles de données pour l'authentification Web Sentinel
"""

from dataclasses import dataclass, field
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta, timezone
from enum import Enum
import uuid
import hashlib
import secrets


class UserRole(str, Enum):
    """Rôles utilisateur dans le système."""
    ADMIN = "admin"
    MEMBER = "member"
    VIEWER = "viewer"
    SYSOP = "sysop"


class AuthProvider(str, Enum):
    """Fournisseurs d'authentification supportés."""
    EMAIL = "email"           # Email + mot de passe
    GOOGLE = "google"         # OAuth Google
    MICROSOFT = "microsoft"   # OAuth Microsoft/Azure AD
    GITHUB = "github"         # OAuth GitHub
    SAML = "saml"            # SAML SSO (Enterprise)


@dataclass
class User:
    """Modèle utilisateur unifié."""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    email: str = ""
    username: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    avatar_url: Optional[str] = None
    
    # Authentification
    password_hash: Optional[str] = None
    email_verified: bool = False
    mfa_enabled: bool = False
    mfa_secret: Optional[str] = None
    
    # Fournisseur auth
    auth_provider: AuthProvider = AuthProvider.EMAIL
    provider_id: Optional[str] = None  # ID chez le fournisseur
    
    # Organisation (Enterprise)
    organization_id: Optional[str] = None
    role: UserRole = UserRole.MEMBER
    
    # Métadonnées
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    last_login: Optional[datetime] = None
    is_active: bool = True
    
    # Préférences
    language: str = "fr"
    timezone: str = "UTC"
    preferences: Dict[str, Any] = field(default_factory=dict)
    
    def __post_init__(self):
        """Validation post-initialisation."""
        if not self.email:
            raise ValueError("Email is required")
        if not self.username:
            self.username = self.email.split('@')[0]
    
    @property
    def display_name(self) -> str:
        """Nom d'affichage de l'utilisateur."""
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name}"
        elif self.first_name:
            return self.first_name
        else:
            return self.username or self.email
    
    @property
    def full_name(self) -> str:
        """Nom complet."""
        parts = []
        if self.first_name:
            parts.append(self.first_name)
        if self.last_name:
            parts.append(self.last_name)
        return " ".join(parts) if parts else self.username or self.email
    
    def set_password(self, password: str) -> None:
        """Définir un mot de passe hashé."""
        salt = secrets.token_hex(16)
        self.password_hash = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt.encode('utf-8'),
            100000  # iterations
        ).hex() + ":" + salt
    
    def verify_password(self, password: str) -> bool:
        """Vérifier un mot de passe."""
        if not self.password_hash:
            return False
        
        try:
            stored_hash, salt = self.password_hash.split(':')
            computed_hash = hashlib.pbkdf2_hmac(
                'sha256',
                password.encode('utf-8'),
                salt.encode('utf-8'),
                100000
            ).hex()
            return computed_hash == stored_hash
        except ValueError:
            return False
    
    def to_dict(self) -> Dict[str, Any]:
        """Sérialiser en dictionnaire."""
        return {
            'id': self.id,
            'email': self.email,
            'username': self.username,
            'first_name': self.first_name,
            'last_name': self.last_name,
            'avatar_url': self.avatar_url,
            'email_verified': self.email_verified,
            'mfa_enabled': self.mfa_enabled,
            'auth_provider': self.auth_provider.value,
            'provider_id': self.provider_id,
            'password_hash': self.password_hash,  # CORRECTION: Inclure le mot de passe hashé
            'organization_id': self.organization_id,
            'role': self.role.value,
            'created_at': self.created_at.isoformat(),
            'updated_at': self.updated_at.isoformat(),
            'last_login': self.last_login.isoformat() if self.last_login else None,
            'is_active': self.is_active,
            'language': self.language,
            'timezone': self.timezone,
            'preferences': self.preferences
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'User':
        """Désérialiser depuis un dictionnaire."""
        # Convertir les dates
        if 'created_at' in data and isinstance(data['created_at'], str):
            data['created_at'] = datetime.fromisoformat(data['created_at'])
        if 'updated_at' in data and isinstance(data['updated_at'], str):
            data['updated_at'] = datetime.fromisoformat(data['updated_at'])
        if 'last_login' in data and data['last_login']:
            data['last_login'] = datetime.fromisoformat(data['last_login'])
        
        # Convertir les enums
        if 'auth_provider' in data:
            data['auth_provider'] = AuthProvider(data['auth_provider'])
        if 'role' in data:
            role_value = data['role']
            if isinstance(role_value, UserRole):
                data['role'] = role_value
            elif isinstance(role_value, str):
                try:
                    data['role'] = UserRole(role_value)
                except ValueError:
                    try:
                        data['role'] = UserRole(role_value.lower())
                    except ValueError:
                        data['role'] = UserRole.MEMBER
        
        return cls(**data)


@dataclass
class AuthToken:
    """Token d'authentification."""
    token: str = field(default_factory=lambda: secrets.token_urlsafe(32))
    user_id: str = ""
    token_type: str = "access"  # access, refresh, verification
    expires_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc) + timedelta(days=30))
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    last_used: Optional[datetime] = None
    device_info: Optional[str] = None
    ip_address: Optional[str] = None
    is_revoked: bool = False
    
    @property
    def is_expired(self) -> bool:
        """Vérifier si le token est expiré."""
        return datetime.now(timezone.utc) > self.expires_at
    
    @property
    def is_valid(self) -> bool:
        """Vérifier si le token est valide."""
        return not self.is_revoked and not self.is_expired
    
    def revoke(self) -> None:
        """Révoquer le token."""
        self.is_revoked = True


@dataclass
class Session:
    """Session utilisateur locale."""
    session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    user: Optional[User] = None
    access_token: Optional[AuthToken] = None
    refresh_token: Optional[AuthToken] = None
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    last_activity: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    
    # Informations de session
    device_name: Optional[str] = None
    ip_address: Optional[str] = None
    user_agent: Optional[str] = None
    
    # État
    is_active: bool = True
    remember_me: bool = False
    
    @property
    def is_authenticated(self) -> bool:
        """Vérifier si la session est authentifiée."""
        return (
            self.user is not None and
            self.access_token is not None and
            self.access_token.is_valid and
            self.is_active
        )
    
    @property
    def is_expired(self) -> bool:
        """Vérifier si la session a expiré."""
        if not self.access_token:
            return True
        
        # Session expire après 8h d'inactivité
        inactivity_limit = timedelta(hours=8)
        if datetime.now(timezone.utc) - self.last_activity > inactivity_limit:
            return True
        
        return self.access_token.is_expired
    
    def update_activity(self) -> None:
        """Mettre à jour l'activité de la session."""
        self.last_activity = datetime.now(timezone.utc)
        if self.access_token:
            self.access_token.last_used = datetime.now(timezone.utc)
    
    def logout(self) -> None:
        """Terminer la session."""
        self.is_active = False
        if self.access_token:
            self.access_token.revoke()
        if self.refresh_token:
            self.refresh_token.revoke()


@dataclass
class Organization:
    """Organisation (pour Enterprise)."""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    name: str = ""
    domain: Optional[str] = None  # Domaine email (ex: acme.com)
    
    # Configuration
    sso_enabled: bool = False
    sso_provider: Optional[str] = None
    sso_config: Dict[str, Any] = field(default_factory=dict)
    
    # Limites
    max_users: int = 50
    allowed_domains: List[str] = field(default_factory=list)
    
    # Métadonnées
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    is_active: bool = True
    
    # Administrateur
    admin_user_id: str = ""
    
    def can_add_user(self, email: str) -> bool:
        """Vérifier si un utilisateur peut être ajouté."""
        if not self.is_active:
            return False
        
        # Vérifier le domaine email
        if self.allowed_domains:
            domain = email.split('@')[1].lower()
            return domain in [d.lower() for d in self.allowed_domains]
        
        return True


@dataclass
class LoginAttempt:
    """Tentative de connexion pour sécurité."""
    email: str = ""
    ip_address: Optional[str] = None
    success: bool = False
    error_reason: Optional[str] = None
    timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    user_agent: Optional[str] = None
