# #region auth_service [C:5] [TYPE Module] [SEMANTICS sqlalchemy, auth, credential, session, jwt]
# @BRIEF Orchestrates credential authentication and ADFS JIT user provisioning.
# @LAYER     Domain
# @RELATION DEPENDS_ON -> [AuthRepository]
# @RELATION DEPENDS_ON -> [verify_password]
# @RELATION DEPENDS_ON -> [create_access_token]
# @RELATION DEPENDS_ON -> [User]
# @RELATION DEPENDS_ON -> [Role]
# @INVARIANT  Authentication succeeds only for active users with valid credentials; issued sessions encode subject and scopes from assigned roles.
# @PRE        Core auth models and security utilities available.
# @POST       User identity verified and session tokens issued according to role scopes.
# @SIDE_EFFECT  Writes last login timestamps and JIT-provisions external users.
# @DATA_CONTRACT  [Credentials | ADFSClaims] -> [UserEntity | SessionToken]

from datetime import UTC, datetime
from typing import Any

from sqlalchemy.orm import Session

from ..core.auth.jwt import create_access_token
from ..core.auth.logger import log_security_event
from ..core.auth.repository import AuthRepository
from ..core.auth.security import verify_password
from ..core.logger import belief_scope
from ..models.auth import User


# #region AuthService [C:3] [TYPE Class]
# @BRIEF Provides high-level authentication services.
# @RELATION DEPENDS_ON -> [AuthRepository]
# @RELATION DEPENDS_ON -> [User]
# @RELATION DEPENDS_ON -> [Role]
class AuthService:
    # region AuthService_init [TYPE Function]
    # @PURPOSE: Initializes the authentication service with repository access over an active DB session.
    # @PRE  db is a valid SQLAlchemy Session instance bound to the auth persistence context.
    # @POST  self.repo is initialized and ready for auth user/role CRUD operations.
    # @SIDE_EFFECT  Allocates AuthRepository and binds it to the provided Session.
    # @DATA_CONTRACT  Input(Session) -> Model(AuthRepository)
    # @PARAM  db (Session) - SQLAlchemy session.
    def __init__(self, db: Session):
        self.db = db
        self.repo = AuthRepository(db)

    # endregion AuthService_init

    # region AuthService.authenticate_user [TYPE Function]
    # @PURPOSE: Validates credentials and account state for local username/password authentication.
    # @PRE  username and password are non-empty credential inputs.
    # @POST  Returns User only when user exists, is active, and password hash verification succeeds; otherwise returns None.
    # @SIDE_EFFECT  Persists last_login update for successful authentications via repository.
    # @DATA_CONTRACT  Input(str username, str password) -> Output(User | None)
    # @RELATION DEPENDS_ON ->[AuthRepository]
    # @RELATION CALLS ->[verify_password]
    # @RELATION DEPENDS_ON ->[User]
    # @PARAM  username (str) - The username.
    # @PARAM  password (str) - The plain password.
    # @RETURN  Optional[User] - The authenticated user or None.
    def authenticate_user(self, username: str, password: str) -> User | None:
        with belief_scope("auth.authenticate_user"):
            user = self.repo.get_user_by_username(username)
            if not user or not user.is_active:
                return None

            if not verify_password(password, user.password_hash):
                return None

            # Update last login
            user.last_login = datetime.now(UTC)
            self.db.commit()
            self.db.refresh(user)

            return user

    # endregion AuthService.authenticate_user

    # region AuthService.create_session [TYPE Function]
    # @PURPOSE: Issues an access token payload for an already authenticated user.
    # @PRE  user is a valid User entity containing username and iterable roles with role.name values.
    # @POST  Returns session dict with non-empty access_token and token_type='bearer'.
    # @SIDE_EFFECT  Generates signed JWT via auth JWT provider.
    # @DATA_CONTRACT  Input(User) -> Output(Dict[str, str]{access_token, token_type})
    # @RELATION CALLS ->[create_access_token]
    # @RELATION DEPENDS_ON ->[User]
    # @RELATION DEPENDS_ON ->[Role]
    # @PARAM  user (User) - The authenticated user.
    # @RETURN  Dict[str, str] - Session data.
    def create_session(self, user: User) -> dict[str, str]:
        with belief_scope("auth.create_session"):
            roles = [role.name for role in user.roles]
            access_token = create_access_token(
                data={"sub": user.username, "scopes": roles}
            )
            return {"access_token": access_token, "token_type": "bearer"}

    # endregion AuthService.create_session

    # region AuthService.provision_adfs_user [TYPE Function]
    # @PURPOSE: Performs ADFS Just-In-Time provisioning and role synchronization from AD group mappings.
    # @PRE  user_info contains identity claims where at least one of 'upn' or 'email' is present; 'groups' may be absent.
    # @POST  Returns persisted user entity with roles synchronized to mapped AD groups and refreshed state.
    # @SIDE_EFFECT  May insert new User, mutate user.roles, commit transaction, and refresh ORM state.
    # @DATA_CONTRACT  Input(Dict[str, Any]{upn|email, email, groups[]}) -> Output(User persisted)
    # @RELATION DEPENDS_ON ->[AuthRepository]
    # @RELATION DEPENDS_ON ->[User]
    # @RELATION DEPENDS_ON ->[Role]
    # @PARAM  user_info (Dict[str, Any]) - Claims from ADFS token.
    # @RETURN  User - The provisioned user.
    def provision_adfs_user(self, user_info: dict[str, Any]) -> User:
        with belief_scope("auth.provision_adfs_user"):
            username = user_info.get("upn") or user_info.get("email")
            email = user_info.get("email")
            groups = user_info.get("groups", [])

            user = self.repo.get_user_by_username(username)
            if not user:
                user = User(
                    username=username,
                    email=email,
                    full_name=user_info.get("name"),
                    auth_source="ADFS",
                    is_active=True,
                    is_ad_user=True,
                )
                self.db.add(user)
                log_security_event("USER_PROVISIONED", username, {"source": "ADFS"})

            # Sync roles from AD groups
            mapped_roles = self.repo.get_roles_by_ad_groups(groups)
            user.roles = mapped_roles

            user.last_login = datetime.now(UTC)
            self.db.commit()
            self.db.refresh(user)

            return user

    # endregion AuthService.provision_adfs_user


# #endregion AuthService

# #endregion auth_service
