"""
Service for accountant-specific security controls: MFA enforcement, step-up auth, session management.
"""
from sqlalchemy.orm import Session
from typing import Optional, Dict, Any, Tuple
from datetime import datetime, timedelta
from ...auth.models.user import User
from ..models.accountant_session import AccountantSession, AccountantActivityLog
from ...shared.utils.role_helpers import is_accountant
from ...shared.config.logging_config import get_logger
import secrets
import hashlib

logger = get_logger(__name__)


class AccountantSecurityService:
    """Service for accountant security controls."""
    
    # Session timeout for accountants (shorter than default)
    ACCOUNTANT_SESSION_TIMEOUT_HOURS = 4  # 4 hours instead of default 14 days
    ACCOUNTANT_IDLE_TIMEOUT_MINUTES = 30  # 30 minutes idle timeout
    
    # Step-up authentication validity
    STEP_UP_VALIDITY_MINUTES = 15  # Step-up auth valid for 15 minutes
    
    @staticmethod
    def requires_mfa(user: User, db: Session) -> bool:
        """Check if user role requires MFA."""
        # Only accountant roles are handled by this service
        return is_accountant(user, db)
    
    @staticmethod
    def is_mfa_enforced(user: User, db: Session) -> Tuple[bool, Optional[str]]:
        """
        Check if MFA is enforced for user.
        Returns (is_enforced: bool, reason: str | None)
        """
        if AccountantSecurityService.requires_mfa(user, db):
            if not user.mfa_enabled:
                return False, "MFA is required for accountant role but not enabled"
            return True, None
        return False, None
    
    @staticmethod
    def create_session(
        db: Session,
        user_id: int,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        device_fingerprint: Optional[str] = None,
        country: Optional[str] = None,
        city: Optional[str] = None
    ) -> AccountantSession:
        """Create a new accountant session."""
        # Generate session token
        session_token = secrets.token_urlsafe(32)
        
        # Calculate expiration (shorter for accountants)
        expires_at = datetime.utcnow() + timedelta(hours=AccountantSecurityService.ACCOUNTANT_SESSION_TIMEOUT_HOURS)
        
        session = AccountantSession(
            user_id=user_id,
            session_token=session_token,
            ip_address=ip_address,
            user_agent=user_agent,
            device_fingerprint=device_fingerprint,
            country=country,
            city=city,
            is_active=True,
            last_activity=datetime.utcnow(),
            step_up_authenticated=False,
            expires_at=expires_at
        )
        
        db.add(session)
        db.flush()
        
        return session
    
    @staticmethod
    def validate_session(
        db: Session,
        session_token: str,
        update_activity: bool = True
    ) -> Optional[AccountantSession]:
        """Validate and update session activity."""
        session = db.query(AccountantSession).filter(
            AccountantSession.session_token == session_token,
            AccountantSession.is_active == True
        ).first()
        
        if not session:
            return None
        
        # Check if session expired
        if session.expires_at < datetime.utcnow():
            session.is_active = False
            db.flush()
            return None
        
        # Check idle timeout
        idle_timeout = datetime.utcnow() - timedelta(minutes=AccountantSecurityService.ACCOUNTANT_IDLE_TIMEOUT_MINUTES)
        if session.last_activity < idle_timeout:
            session.is_active = False
            db.flush()
            return None
        
        # Update last activity
        if update_activity:
            session.last_activity = datetime.utcnow()
            db.flush()
        
        return session
    
    @staticmethod
    def require_step_up(
        db: Session,
        user_id: int,
        session_token: Optional[str] = None,
        action_description: str = "high-risk action",
        enforce_role_check: bool = True,
    ) -> Tuple[bool, Optional[str]]:
        """
        Check if step-up authentication is required.
        Returns (requires_step_up: bool, reason: str | None)
        """
        user = db.query(User).filter(User.id == user_id).first()
        if enforce_role_check:
            # Only enforce step-up for accountant roles when role check is enabled
            if user and not is_accountant(user, db):
                return False, None
        # If no session token provided, try to find the most recent active session for this user
        if not session_token:
            active_session = db.query(AccountantSession).filter(
                AccountantSession.user_id == user_id,
                AccountantSession.is_active == True,
                AccountantSession.expires_at > datetime.utcnow()
            ).order_by(AccountantSession.last_activity.desc()).first()
            
            if active_session:
                session_token = active_session.session_token
            else:
                return True, "Step-up authentication required for this action"
        
        session = AccountantSecurityService.validate_session(db, session_token, update_activity=False)
        if not session:
            return True, "Invalid or expired session"
        
        if session.user_id != user_id:
            return True, "Session user mismatch"
        
        # Check if step-up is still valid
        if session.step_up_authenticated and session.step_up_expires_at:
            if session.step_up_expires_at > datetime.utcnow():
                return False, None  # Step-up still valid
            else:
                session.step_up_authenticated = False
                db.flush()
        
        return True, f"Step-up authentication required for {action_description}"
    
    @staticmethod
    def complete_step_up(
        db: Session,
        session_token: str,
        user_id: int
    ) -> bool:
        """Mark step-up authentication as completed."""
        session = db.query(AccountantSession).filter(
            AccountantSession.session_token == session_token,
            AccountantSession.user_id == user_id,
            AccountantSession.is_active == True
        ).first()
        
        if not session:
            return False
        
        session.step_up_authenticated = True
        session.step_up_expires_at = datetime.utcnow() + timedelta(
            minutes=AccountantSecurityService.STEP_UP_VALIDITY_MINUTES
        )
        
        # Use flush to ensure changes are visible in the same transaction
        # The route handler will commit
        db.flush()
        return True
    
    @staticmethod
    def log_activity(
        db: Session,
        user_id: int,
        activity_type: str,
        activity_description: str,
        session_id: Optional[int] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        country: Optional[str] = None,
        city: Optional[str] = None,
        risk_level: str = 'low',
        is_unusual: bool = False,
        metadata: Optional[Dict[str, Any]] = None
    ) -> AccountantActivityLog:
        """Log accountant activity for security monitoring."""
        log = AccountantActivityLog(
            user_id=user_id,
            session_id=session_id,
            activity_type=activity_type,
            activity_description=activity_description,
            ip_address=ip_address,
            user_agent=user_agent,
            country=country,
            city=city,
            risk_level=risk_level,
            is_unusual=is_unusual,
            activity_metadata=metadata or {}
        )
        
        db.add(log)
        db.flush()
        
        # Alert on high-risk or unusual activity
        if risk_level in ['high', 'critical'] or is_unusual:
            logger.warning(
                f"High-risk accountant activity detected: {activity_type} by user {user_id}",
                extra={
                    'user_id': user_id,
                    'activity_type': activity_type,
                    'risk_level': risk_level,
                    'is_unusual': is_unusual,
                    'ip_address': ip_address
                }
            )
        
        return log
    
    @staticmethod
    def detect_unusual_activity(
        db: Session,
        user_id: int,
        ip_address: Optional[str] = None,
        country: Optional[str] = None
    ) -> bool:
        """Detect if current activity is unusual based on user history."""
        # Get user's recent activity (last 30 days)
        thirty_days_ago = datetime.utcnow() - timedelta(days=30)
        
        recent_activities = db.query(AccountantActivityLog).filter(
            AccountantActivityLog.user_id == user_id,
            AccountantActivityLog.created_at >= thirty_days_ago
        ).all()
        
        if not recent_activities:
            # First activity - not unusual
            return False
        
        # Check for new IP address
        if ip_address:
            known_ips = set(act.ip_address for act in recent_activities if act.ip_address)
            if ip_address not in known_ips and len(known_ips) > 0:
                return True
        
        # Check for new country
        if country:
            known_countries = set(act.country for act in recent_activities if act.country)
            if country not in known_countries and len(known_countries) > 0:
                return True
        
        return False
    
    @staticmethod
    def revoke_session(
        db: Session,
        session_token: str
    ) -> bool:
        """Revoke an accountant session."""
        session = db.query(AccountantSession).filter(
            AccountantSession.session_token == session_token
        ).first()
        
        if not session:
            return False
        
        session.is_active = False
        db.flush()
        return True
    
    @staticmethod
    def revoke_all_user_sessions(
        db: Session,
        user_id: int
    ) -> int:
        """Revoke all active sessions for a user."""
        count = db.query(AccountantSession).filter(
            AccountantSession.user_id == user_id,
            AccountantSession.is_active == True
        ).update({'is_active': False})
        
        db.flush()
        return count


# Singleton instance
accountant_security_service = AccountantSecurityService()

