# src/application/services/auth.py
from datetime import datetime, timezone, timedelta
from jose import jwt, JWTError
from passlib.context import CryptContext
from src.domain.ports.repositories.user_repository import UserRepositoryPort 
from src.domain.ports.repositories.token_repository import TokenRepositoryPort
from src.domain.entities.user import User
from src.config import Settings
import uuid
from src.application.ports.authentication_service_port import AuthServicePort
from src.application.dtos.authentication import (
    RegisterRequestDTO,
    LoginRequestDTO,
    UserResponseDTO,
    TokenResponseDTO
)

class AuthService(AuthServicePort):
    def __init__(
        self,
        user_repo: UserRepositoryPort,
        token_repo: TokenRepositoryPort,
        secret_key: str = Settings().JWT_SECRET,
        algorithm: str = "HS256",
        expires_minutes: int = 30
    ):
        self._user_repo = user_repo
        self._token_repo = token_repo  
        self._secret_key = secret_key
        self._algorithm = algorithm
        self._expires_minutes = expires_minutes
        self._pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

    async def register(self, register_dto: RegisterRequestDTO) -> UserResponseDTO:
        """
        Register a new user with email and password
        Returns UserResponseDTO containing user details
        """
        if await self._user_repo.get_by_email(register_dto.email):
            raise ValueError("Email already registered")
        
        user = User(
            id=str(uuid.uuid4()),
            email=register_dto.email,
            password_hash=self._hash_password(register_dto.password),
            is_active=True
        )
        
        await self._user_repo.create_user(user)
        return self._user_to_dto(user)

    async def login(self, login_dto: LoginRequestDTO) -> TokenResponseDTO:
        """
        Authenticate user and return JWT token
        Returns TokenResponseDTO containing access token
        """
        user = await self._user_repo.get_by_email(login_dto.email)
        if not user or not self._verify_password(login_dto.password, user.password_hash):
            raise ValueError("Invalid credentials")
        
        access_token = self._create_access_token(user.email)
        return TokenResponseDTO(access_token=access_token)

    async def logout(self, token: str) -> None:
        """Invalidate a JWT token by adding it to blacklist"""
        try:
            payload = jwt.decode(token, self._secret_key, algorithms=[self._algorithm])
            exp = payload.get("exp")
            if exp:
                expires_at = datetime.fromtimestamp(exp, tz=timezone.utc)
                await self._token_repo.add_to_blacklist(token, expires_at)
        except JWTError:
            pass  # Token is invalid anyway

    async def validate_token(self, token: str) -> bool:
        """
        Check if token is valid and not blacklisted
        Returns boolean indicating token validity
        """
        try:
            if await self._token_repo.is_blacklisted(token):
                return False
                
            jwt.decode(token, self._secret_key, algorithms=[self._algorithm])
            return True
        except JWTError:
            return False

    def _hash_password(self, password: str) -> str:
        """Hash password using bcrypt"""
        return self._pwd_context.hash(password)

    def _verify_password(self, plain_password: str, hashed_password: str) -> bool:
        """Verify password against stored hash"""
        return self._pwd_context.verify(plain_password, hashed_password)

    def _create_access_token(self, email: str) -> str:
        """Create JWT token with expiration"""
        expires = datetime.now(timezone.utc) + timedelta(minutes=self._expires_minutes)
        return jwt.encode(
            {"sub": email, "exp": expires},
            self._secret_key,
            algorithm=self._algorithm
        )

    def _user_to_dto(self, user: User) -> UserResponseDTO:
        """Convert User entity to UserResponseDTO"""
        return UserResponseDTO(
            id=user.id,
            email=user.email,
            is_active=user.is_active
        )