package de.effigenix.application.usermanagement;

import de.effigenix.application.usermanagement.command.AuthenticateCommand;
import de.effigenix.application.usermanagement.dto.SessionToken;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Optional;

/**
 * Use Case: Authenticate a user (login).
 *
 * Returns a JWT session token on success.
 * Logs all authentication attempts for security auditing.
 */
public class AuthenticateUser {

    private final UserRepository userRepository;
    private final PasswordHasher passwordHasher;
    private final SessionManager sessionManager;
    private final AuditLogger auditLogger;
    private final UnitOfWork unitOfWork;

    public AuthenticateUser(
            UserRepository userRepository,
            PasswordHasher passwordHasher,
            SessionManager sessionManager,
            AuditLogger auditLogger,
            UnitOfWork unitOfWork
    ) {
        this.userRepository = userRepository;
        this.passwordHasher = passwordHasher;
        this.sessionManager = sessionManager;
        this.auditLogger = auditLogger;
        this.unitOfWork = unitOfWork;
    }

    public Result<UserError, SessionToken> execute(AuthenticateCommand cmd) {
        // 1. Find user by username
        return userRepository.findByUsername(cmd.username())
                .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                .flatMap(optUser -> {
                    if (optUser.isEmpty()) {
                        auditLogger.log(AuditEvent.LOGIN_FAILED, "Username not found: " + cmd.username());
                        return Result.failure(new UserError.InvalidCredentials());
                    }
                    return authenticateUser(optUser.get(), cmd);
                });
    }

    private Result<UserError, SessionToken> authenticateUser(User user, AuthenticateCommand cmd) {
        // 2. Check user status
        if (user.status() == UserStatus.LOCKED) {
            auditLogger.log(AuditEvent.LOGIN_BLOCKED, user.id().value(), ActorId.of(user.id().value()));
            return Result.failure(new UserError.UserLocked(user.id()));
        }

        if (user.status() == UserStatus.INACTIVE) {
            auditLogger.log(AuditEvent.LOGIN_FAILED, "User inactive: " + user.username());
            return Result.failure(new UserError.UserInactive(user.id()));
        }

        // 3. Verify password (BCrypt)
        if (!passwordHasher.verify(cmd.password(), user.passwordHash())) {
            auditLogger.log(AuditEvent.LOGIN_FAILED, user.id().value(), ActorId.of(user.id().value()));
            return Result.failure(new UserError.InvalidCredentials());
        }

        // 4. Create JWT session
        SessionToken token = sessionManager.createSession(user);

        // 5. Update last login timestamp (immutable)
        return user.withLastLogin(OffsetDateTime.now(ZoneOffset.UTC))
                .flatMap(updated -> unitOfWork.executeAtomically(() ->
                        userRepository.save(updated)
                                .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                                .map(ignored -> {
                                    auditLogger.log(AuditEvent.LOGIN_SUCCESS, updated.id().value(), ActorId.of(updated.id().value()));
                                    return token;
                                })));
    }
}
