from typing import Optional, Dict, Tuple
from datetime import datetime
from sqlalchemy.orm import Session
import logging

from genovation_advanced_auth.models.user import User
from genovation_advanced_auth.core.security import (
    hash_password,
    verify_password,
    create_access_token,
    create_refresh_token,
    decode_token
)
from genovation_advanced_auth.core.config import AuthConfig, get_config
from genovation_advanced_auth.exceptions import (
    InvalidCredentialsError,
    UserNotFoundError,
    UserAlreadyExistsError
)

logger = logging.getLogger(__name__)


class AuthService:
    """Service for authentication operations."""
    
    def __init__(self, db: Session, config: Optional[AuthConfig] = None):
        self.db = db
        self.config = config or get_config()
    
    def get_user_by_username(self, username: str) -> Optional[User]:
        """Get user by username."""
        return self.db.query(User).filter(User.username == username).first()
    
    def get_user_by_email(self, email: str) -> Optional[User]:
        """Get user by email."""
        return self.db.query(User).filter(User.email == email).first()
    
    def register_user(
        self,
        username: str,
        email: str,
        password: str,
        full_name: Optional[str] = None
    ) -> User:
        """Register a new user."""
        # Check if user exists
        if self.get_user_by_username(username):
            raise UserAlreadyExistsError()
        
        if self.get_user_by_email(email):
            raise UserAlreadyExistsError()
        
        # Create user
        hashed_pwd = hash_password(password)
        user = User(
            username=username,
            email=email,
            hashed_password=hashed_pwd,
            full_name=full_name,
            is_active=True
        )
        
        self.db.add(user)
        self.db.commit()
        self.db.refresh(user)
        
        logger.info(f"User registered: {username}")
        return user
    
    def authenticate_user(self, username: str, password: str) -> Tuple[User, bool]:
        """
        Authenticate user with username/password.
        
        Returns:
            Tuple of (User, requires_mfa)
        """
        # Try username first, then email
        user = self.get_user_by_username(username)
        if not user:
            user = self.get_user_by_email(username)
        
        if not user:
            raise InvalidCredentialsError()
        
        if not verify_password(password, user.hashed_password):
            raise InvalidCredentialsError()
        
        if not user.is_active:
            raise ValueError("User account is disabled")
        
        # Check if MFA is required
        requires_mfa = user.mfa_enabled and user.mfa_secret is not None
        
        return user, requires_mfa
    
    def create_tokens(self, user: User) -> Dict[str, str]:
        """Create access and refresh tokens for user."""
        token_data = {
            "sub": str(user.id),
            "username": user.username,
            "is_superuser": user.is_superuser
        }
        
        access_token = create_access_token(
            data=token_data,
            config=self.config
        )
        
        refresh_token = create_refresh_token(
            data=token_data,
            config=self.config
        )
        
        # Update last login
        user.last_login = datetime.utcnow()
        self.db.commit()
        
        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "bearer"
        }
    
    def refresh_access_token(self, refresh_token: str) -> Dict[str, str]:
        """Create new access token from refresh token."""
        payload = decode_token(
            refresh_token,
            config=self.config
        )
        
        if payload.get("type") != "refresh":
            raise ValueError("Invalid token type")
        
        user_id = int(payload.get("sub"))
        user = self.db.query(User).filter(User.id == user_id).first()
        
        if not user:
            raise UserNotFoundError()
        
        return self.create_tokens(user)
    
    def get_current_user(self, token: str) -> User:
        """Get current user from token."""
        payload = decode_token(token, config=self.config)
        user_id = int(payload.get("sub"))
        
        user = self.db.query(User).filter(User.id == user_id).first()
        if not user:
            raise UserNotFoundError()
        
        return user
