package marketing.heyday.hartmann.fotodocumentation.core.service;

import static marketing.heyday.hartmann.fotodocumentation.core.model.JwtRefreshToken.*;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;

import io.jsonwebtoken.Claims;
import jakarta.inject.Inject;
import marketing.heyday.hartmann.fotodocumentation.core.model.JwtRefreshToken;
import marketing.heyday.hartmann.fotodocumentation.core.model.Right;
import marketing.heyday.hartmann.fotodocumentation.core.model.User;
import marketing.heyday.hartmann.fotodocumentation.core.query.Param;
import marketing.heyday.hartmann.fotodocumentation.core.utils.JwtTokenUtil;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.TokenPairValue;

/**
 * 
 * <p>Copyright: Copyright (c) 2024</p>
 * <p>Company: heyday Marketing GmbH</p>
 * @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
 * @version 1.0
 * 
 * created: 21 Jan 2026
 */

public class JwtTokenService extends AbstractService {
	private static final Long EXPIRED_DELTA = 30L * 24 * 60 * 60 * 1000;
	
	@Inject
	private JwtTokenUtil jwtTokenUtil;

	public TokenPairValue generateTokenPair(User user, String deviceInfo, String ipAddress) {
		String accessToken = jwtTokenUtil.generateAccessToken(
				user.getUserId(),
				user.getUsername(),
				user.getRights().stream().map(Right::getCode).toList());

		String refreshToken = jwtTokenUtil.generateRefreshToken(user.getUserId());

		// Store refresh token (optional - for revocation support)                                                                                               
		var refreshTokenEntity = new JwtRefreshToken.Builder()
				.user(user)
				.tokenHash(hashToken(refreshToken))
				.deviceInfo(deviceInfo)
				.ipAddress(ipAddress)
				.issuedAt(new Date())
				.expiresAt(new Date(System.currentTimeMillis() + EXPIRED_DELTA)).build();
		entityManager.persist(refreshTokenEntity);

		return new TokenPairValue(accessToken, refreshToken);
	}

	public String refreshAccessToken(String refreshToken) {
		Claims claims = jwtTokenUtil.validateAndExtractClaims(refreshToken);
		Long userId = Long.parseLong(claims.getSubject());

		// Verify refresh token exists and not revoked                                                                                                           
		String tokenHash = hashToken(refreshToken);

		Optional<JwtRefreshToken> tokenEntityOpt = queryService.callNamedQuerySingleResult(FIND_BY_HASH_REVOKE_NULL, new Param(PARAM_HASH, tokenHash));
		if (tokenEntityOpt.isEmpty()) {
			// FIXME: do error handling
		}

		var tokenEntity = tokenEntityOpt.get();                                                                                                                               
		tokenEntity.setLastUsedAt(new Date());
		entityManager.merge(tokenEntity);

		User user = entityManager.find(User.class, userId);
		return jwtTokenUtil.generateAccessToken(user.getUserId(), user.getUsername(), user.getRights().stream().map(Right::getCode).toList());
	}

	public void revokeRefreshToken(String refreshToken) {
		String tokenHash = hashToken(refreshToken);
		Optional<JwtRefreshToken> tokenEntityOpt = queryService.callNamedQuerySingleResult(FIND_BY_HASH, new Param(PARAM_HASH, tokenHash));
		if (tokenEntityOpt.isPresent()) {
			JwtRefreshToken tokenEntity = tokenEntityOpt.get();
			tokenEntity.setRevokedAt(new Date());
			entityManager.merge(tokenEntity);
		}
	}

	public void revokeAllUserTokens(Long userId) {
		queryService.callNamedQueryUpdate(REVOKE_ALL_USER, new Param(PARAM_DATE, new Date()), new Param(PARAM_USER_ID, userId));
	}

	private String hashToken(String token) {
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-256");
			byte[] hash = digest.digest(token.getBytes(StandardCharsets.UTF_8));
			return Base64.getEncoder().encodeToString(hash);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}
}
