package app.mealsmadeeasy.api.auth;

import app.mealsmadeeasy.api.jwt.JwtService;
import app.mealsmadeeasy.api.user.User;
import jakarta.transaction.Transactional;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;

import java.time.OffsetDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class AuthServiceImpl implements AuthService {

    private final AuthenticationManager authenticationManager;
    private final JwtService jwtService;
    private final RefreshTokenRepository refreshTokenRepository;
    private final long refreshTokenLifetime;

    public AuthServiceImpl(
            AuthenticationManager authenticationManager,
            JwtService jwtService,
            RefreshTokenRepository refreshTokenRepository,
            @Value("${app.mealsmadeeasy.api.security.refresh-token-lifetime}") Long refreshTokenLifetime
    ) {
        this.authenticationManager = authenticationManager;
        this.jwtService = jwtService;
        this.refreshTokenRepository = refreshTokenRepository;
        this.refreshTokenLifetime = refreshTokenLifetime;
    }

    private RefreshToken createRefreshToken(User principal) {
        final RefreshToken refreshTokenDraft = new RefreshToken();
        refreshTokenDraft.setToken(UUID.randomUUID());
        refreshTokenDraft.setIssued(OffsetDateTime.now());
        refreshTokenDraft.setExpiration(OffsetDateTime.now().plusSeconds(this.refreshTokenLifetime));
        refreshTokenDraft.setOwner(principal);
        return this.refreshTokenRepository.save(refreshTokenDraft);
    }

    @Override
    public LoginDetails login(String username, String password) throws LoginException {
        try {
            final Authentication authentication = this.authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(username, password)
            );
            final User principal = (User) authentication.getPrincipal();
            return new LoginDetails(
                    username,
                    this.jwtService.generateAccessToken(username),
                    this.createRefreshToken(principal)
            );
        } catch (AuthenticationException e) {
            throw new LoginException(LoginExceptionReason.INVALID_CREDENTIALS, e);
        }
    }

    @Override
    @Transactional
    public void logout(UUID refreshToken) {
        this.refreshTokenRepository.findByToken(refreshToken).ifPresent(this.refreshTokenRepository::delete);
    }

    @Override
    @Transactional
    public LoginDetails refresh(@Nullable UUID refreshToken) throws LoginException {
        if (refreshToken == null) {
            throw new LoginException(LoginExceptionReason.NO_REFRESH_TOKEN, "No refresh token provided.");
        }

        final RefreshToken old = this.refreshTokenRepository.findByToken(refreshToken)
                .orElseThrow(() -> new LoginException(
                        LoginExceptionReason.INVALID_REFRESH_TOKEN,
                        "No such refresh token: " + refreshToken
                ));
        if (old.getRevoked() || old.getDeleted()) {
            throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token.");
        }
        if (old.getExpiration().isBefore(OffsetDateTime.now())) {
            throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "Refresh token is expired.");
        }

        final User principal = old.getOwner();
        old.setDeleted(true);
        this.refreshTokenRepository.save(old);

        final String username = principal.getUsername();
        return new LoginDetails(
                username,
                this.jwtService.generateAccessToken(username),
                this.createRefreshToken(principal)
        );
    }

    @Scheduled(fixedDelay = 60, timeUnit = TimeUnit.SECONDS)
    public void cleanUpDeletedRefreshTokens() {
        this.refreshTokenRepository.deleteAllWhereSoftDeleted();
    }

}
