"""
Authentication Service - Oracle Database Integration pentru ROA2WEB

Acest modul integrează sistemul de autentificare JWT cu baza de date Oracle,
reutilizând funcționalitatea existentă din aplicația Flask originală.

Funcționalități:
- Verificare utilizatori prin pack_drepturi.verificautilizator
- Obținere lista firmelor din vdef_util_grup
- Gestionarea sesiunilor și permisiunilor utilizatorilor
- Caching pentru performanță optimă
"""

import logging
import hashlib
from typing import Optional, List, Dict, Any, Tuple
from datetime import datetime, timedelta
import asyncio
from contextlib import asynccontextmanager

# Import shared database pool
# IMPORTANT: Use shared.database.oracle_pool to ensure singleton consistency
# DO NOT use relative imports like 'database.oracle_pool'
from shared.database.oracle_pool import oracle_pool
from .jwt_handler import jwt_handler
from .models import TokenResponse, CurrentUser

logger = logging.getLogger(__name__)


class AuthenticationError(Exception):
    """Excepție pentru erorile de autentificare"""
    pass


class UserAuthService:
    """
    Serviciu pentru autentificarea utilizatorilor folosind Oracle Database
    
    Acest serviciu integrează:
    - Verificarea credențialelor prin pack_drepturi.verificautilizator
    - Obținerea listei de firme prin vdef_util_grup
    - Generarea token-urilor JWT
    - Cache pentru performanță
    """
    
    def __init__(self):
        """Inițializează serviciul de autentificare"""
        self._user_cache: Dict[str, Dict[str, Any]] = {}
        self._cache_ttl = timedelta(minutes=15)  # Cache 15 minute
    
    def _get_cache_key(self, username: str) -> str:
        """Generează cheia de cache pentru utilizator"""
        return f"auth_user_{username.lower()}"
    
    def _is_cache_valid(self, cache_entry: Dict[str, Any]) -> bool:
        """Verifică dacă entry-ul din cache este încă valid"""
        if not cache_entry or 'timestamp' not in cache_entry:
            return False
        
        cache_time = cache_entry['timestamp']
        return datetime.now() - cache_time < self._cache_ttl
    
    def _get_cached_user_data(self, username: str) -> Optional[Dict[str, Any]]:
        """Obține datele utilizatorului din cache dacă sunt valide"""
        cache_key = self._get_cache_key(username)
        cache_entry = self._user_cache.get(cache_key)
        
        if self._is_cache_valid(cache_entry):
            logger.debug(f"Cache hit for user {username}")
            return cache_entry['data']
        
        return None
    
    def _cache_user_data(self, username: str, data: Dict[str, Any]) -> None:
        """Salvează datele utilizatorului în cache"""
        cache_key = self._get_cache_key(username)
        self._user_cache[cache_key] = {
            'data': data,
            'timestamp': datetime.now()
        }
        logger.debug(f"Cached data for user {username}")
    
    async def verify_user_credentials(self, username: str, password: str) -> bool:
        """
        Verifică credențialele utilizatorului folosind pack_drepturi.verificautilizator
        
        Args:
            username: Numele utilizatorului
            password: Parola utilizatorului
            
        Returns:
            True dacă credențialele sunt corecte, False altfel
            
        Raises:
            AuthenticationError: Dacă apar erori în procesul de verificare
        """
        try:
            async with oracle_pool.get_connection() as connection:
                with connection.cursor() as cursor:
                    # Apelarea procedurii pack_drepturi.verificautilizator
                    # Această procedură returnează ID-ul utilizatorului cu checksum pentru succes, -1 pentru eșec
                    cursor.execute("""
                        SELECT pack_drepturi.verificautilizator(:username, :password) 
                        FROM DUAL
                    """, {
                        'username': username.upper(),
                        'password': password
                    })
                    
                    result = cursor.fetchone()
                    verification_result = result[0] if result else -1
                    
                    # Interpretarea rezultatului conform logicii VFP:
                    # -1 = invalid credentials
                    # > 0 = valid user ID with checksum
                    # < -1000000 = admin/super user
                    is_valid = verification_result != -1
                    
                    if is_valid:
                        # Extrage ID-ul real al utilizatorului conform logicii VFP
                        if verification_result < -1000000:
                            # Admin/Super user
                            user_id = verification_result + 1000000
                            logger.info(f"Admin/Super user {username} authenticated successfully (ID: {user_id})")
                        else:
                            # User normal - extrage ID-ul din checksum
                            user_id = int(verification_result / 100)
                            logger.info(f"User {username} authenticated successfully (ID: {user_id}, verification: {verification_result})")
                    else:
                        logger.warning(f"Authentication failed for user {username}")
                    
                    return is_valid
                    
        except Exception as e:
            logger.error(f"Database error during authentication for user {username}: {str(e)}")
            raise AuthenticationError(f"Database authentication error: {str(e)}")
    
    async def get_user_companies(self, username: str) -> List[str]:
        """
        Obține lista firmelor la care utilizatorul are acces din V_NOM_FIRME
        folosind ID-ul utilizatorului din UTILIZATORI
        
        Args:
            username: Numele utilizatorului
            
        Returns:
            Lista codurilor firmelor la care utilizatorul are acces
            
        Raises:
            AuthenticationError: Dacă apar erori în procesul de obținere
        """
        # Verifică cache-ul mai întâi
        cached_data = self._get_cached_user_data(username)
        if cached_data and 'companies' in cached_data:
            return cached_data['companies']
        
        try:
            async with oracle_pool.get_connection() as connection:
                with connection.cursor() as cursor:
                    try:
                        # Debug: să vedem ce utilizatori există în tabela UTILIZATORI
                        cursor.execute("""
                            SELECT ID_UTIL, UTILIZATOR 
                            FROM UTILIZATORI 
                            WHERE UPPER(UTILIZATOR) LIKE '%MARIUS%'
                            ORDER BY UTILIZATOR
                        """)
                        
                        debug_users = cursor.fetchall()
                        logger.info(f"DEBUG: Users with MARIUS in name: {debug_users}")
                        
                        # Primul pas: obținem ID-ul utilizatorului din UTILIZATORI
                        cursor.execute("""
                            SELECT ID_UTIL, UTILIZATOR
                            FROM UTILIZATORI 
                            WHERE UPPER(UTILIZATOR) = :username
                        """, {'username': username.upper()})
                        
                        user_row = cursor.fetchone()
                        if not user_row:
                            logger.warning(f"User {username} not found in UTILIZATORI table")
                            # Să încercăm să găsim utilizatori similari
                            cursor.execute("""
                                SELECT ID_UTIL, UTILIZATOR 
                                FROM UTILIZATORI 
                                WHERE UPPER(UTILIZATOR) LIKE :username_pattern
                                ORDER BY UTILIZATOR
                            """, {'username_pattern': f'%{username.upper()}%'})
                            similar_users = cursor.fetchall()
                            logger.info(f"Similar users found: {similar_users}")
                            return []
                        
                        user_id = user_row[0]
                        actual_name = user_row[1]
                        logger.info(f"Found user {username} with ID: {user_id}, actual name: {actual_name}")
                        
                        # Al doilea pas: obținem firmele folosind query-ul corect (cu ID_FIRMA)
                        cursor.execute("""
                            SELECT A.ID_FIRMA, A.FIRMA
                            FROM V_NOM_FIRME A
                            WHERE A.ID_FIRMA IN (
                                SELECT ID_FIRMA
                                FROM VDEF_UTIL_FIRME
                                WHERE ID_PROGRAM = 2
                                  AND ID_UTIL = :user_id
                            )
                            ORDER BY A.FIRMA
                        """, {'user_id': user_id})
                        
                        companies_rows = cursor.fetchall()
                        companies = [str(row[0]) for row in companies_rows if row[0]]
                        
                        if not companies:
                            logger.warning(f"No companies found for user {username} (ID: {user_id})")
                            return []
                        
                        logger.info(f"User {username} has access to {len(companies)} companies: {companies}")
                        
                    except Exception as e:
                        logger.error(f"Could not query companies for user {username}: {e}")
                        # În caz de eroare, returnăm listă goală în loc de TEST_COMPANY
                        return []
                    
                    # Cache rezultatul
                    self._cache_user_data(username, {'companies': companies})
                    
                    return companies
                    
        except Exception as e:
            logger.error(f"Database error getting companies for user {username}: {str(e)}")
            raise AuthenticationError(f"Error retrieving user companies: {str(e)}")
    
    async def get_user_permissions(self, username: str, company: str) -> List[str]:
        """
        Obține permisiunile utilizatorului pentru o anumită firmă
        
        Args:
            username: Numele utilizatorului
            company: Codul firmei
            
        Returns:
            Lista permisiunilor pentru firma specificată
        """
        # Implementare de bază - poate fi extinsă în viitor
        companies = await self.get_user_companies(username)
        
        # Dacă nu există companii sau compania nu este în listă, returnează permisiuni minime
        if not companies or company not in companies:
            return ["read"] if not companies else []
        
        # Pentru moment, toți utilizatorii autentificați au permisiuni de citire
        # Acest sistem poate fi extins cu permisiuni granulare în viitor
        return ["read", "reports"]
    
    async def authenticate_and_create_tokens(
        self, 
        username: str, 
        password: str
    ) -> Tuple[bool, Optional[TokenResponse], Optional[str]]:
        """
        Autentifică utilizatorul și creează token-urile JWT
        
        Args:
            username: Numele utilizatorului
            password: Parola utilizatorului
            
        Returns:
            Tuple cu (success, token_response, error_message)
        """
        try:
            # Verifică credențialele
            is_valid = await self.verify_user_credentials(username, password)
            
            if not is_valid:
                return False, None, "Invalid username or password"
            
            # Obține firmele utilizatorului
            companies = await self.get_user_companies(username)
            
            # Nu blocăm login-ul dacă utilizatorul nu are firme - îl lăsăm să vadă mesajul în frontend
            if not companies:
                logger.info(f"User {username} has no companies assigned - allowing login but with empty companies list")
            
            # Obține permisiunile (pentru prima firmă ca default sau lista goală)
            permissions = await self.get_user_permissions(username, companies[0] if companies else "")
            
            # Creează token-urile folosind jwt_handler
            jwt_tokens = jwt_handler.create_token_response(
                username=username,
                companies=companies,
                user_id=None,  # Poate fi adăugat în viitor dacă avem user_id în DB
                permissions=permissions
            )
            
            # Creează obiectul CurrentUser
            current_user = CurrentUser(
                username=username,
                user_id=None,
                companies=companies,
                permissions=permissions
            )
            
            # Creează TokenResponse-ul complet cu user info
            token_response = TokenResponse(
                access_token=jwt_tokens.access_token,
                refresh_token=jwt_tokens.refresh_token,
                token_type=jwt_tokens.token_type,
                expires_in=jwt_tokens.expires_in,
                user=current_user
            )
            
            logger.info(f"Successfully created tokens for user {username}")
            return True, token_response, None
            
        except AuthenticationError as e:
            logger.error(f"Authentication error for user {username}: {str(e)}")
            return False, None, str(e)
        except Exception as e:
            logger.error(f"Unexpected error during authentication for user {username}: {str(e)}")
            return False, None, "Internal authentication error"
    
    async def validate_user_company_access(self, username: str, company: str) -> bool:
        """
        Validează dacă utilizatorul are acces la o anumită firmă
        
        Args:
            username: Numele utilizatorului
            company: Codul firmei de verificat
            
        Returns:
            True dacă utilizatorul are acces, False altfel
        """
        try:
            companies = await self.get_user_companies(username)
            has_access = company in companies
            
            if not has_access:
                logger.warning(f"User {username} attempted to access unauthorized company {company}")
            
            return has_access
            
        except Exception as e:
            logger.error(f"Error validating company access for user {username}: {str(e)}")
            return False
    
    async def refresh_user_data(self, username: str) -> bool:
        """
        Reîmprospătează datele utilizatorului din cache
        
        Args:
            username: Numele utilizatorului
            
        Returns:
            True dacă refresh-ul a fost cu succes
        """
        try:
            # Șterge din cache
            cache_key = self._get_cache_key(username)
            if cache_key in self._user_cache:
                del self._user_cache[cache_key]
            
            # Reîncarcă datele
            await self.get_user_companies(username)
            logger.info(f"Refreshed user data for {username}")
            return True
            
        except Exception as e:
            logger.error(f"Error refreshing user data for {username}: {str(e)}")
            return False
    
    def clear_cache(self) -> None:
        """Șterge tot cache-ul utilizatorilor"""
        self._user_cache.clear()
        logger.info("User cache cleared")
    
    def get_cache_stats(self) -> Dict[str, Any]:
        """Returnează statistici despre cache"""
        total_entries = len(self._user_cache)
        valid_entries = sum(
            1 for entry in self._user_cache.values() 
            if self._is_cache_valid(entry)
        )
        
        return {
            'total_entries': total_entries,
            'valid_entries': valid_entries,
            'cache_hit_ratio': valid_entries / total_entries if total_entries > 0 else 0
        }


# Instance globală pentru folosire în toate aplicațiile
auth_service = UserAuthService()