package org.brapi.test.BrAPITestServer.system.service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.brapi.test.BrAPITestServer.system.config.JwtProperties;
import org.brapi.test.BrAPITestServer.system.dto.TokenResponse;
import org.brapi.test.BrAPITestServer.system.dto.UserResponse;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.entity.UserSessionEntity;
import org.brapi.test.BrAPITestServer.system.repository.UserSessionRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HexFormat;
import java.util.Optional;
import java.util.UUID;

@Service
public class TokenService {

    private static final DateTimeFormatter ISO_FORMATTER =
            DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneOffset.UTC);

    private final JwtProperties jwtProperties;
    private final UserSessionRepository userSessionRepository;

    public TokenService(JwtProperties jwtProperties, UserSessionRepository userSessionRepository) {
        this.jwtProperties = jwtProperties;
        this.userSessionRepository = userSessionRepository;
    }

    @Transactional
    public TokenResponse issueToken(SysUserEntity user) {
        Instant issuedAt = Instant.now();
        Instant expiresAt = issuedAt.plus(jwtProperties.getAccessTokenExpireMinutes(), ChronoUnit.MINUTES);
        String jti = generateJti();
        String accessToken = createAccessToken(user, jti, issuedAt, expiresAt);

        UserSessionEntity session = new UserSessionEntity();
        session.setUserId(user.getId());
        session.setJti(jti);
        session.setTokenHash(hashToken(accessToken));
        session.setExpiresAt(expiresAt);
        userSessionRepository.save(session);

        TokenResponse response = new TokenResponse();
        response.setAccessToken(accessToken);
        response.setExpiresAt(ISO_FORMATTER.format(expiresAt));
        response.setUser(toUserResponse(user));
        return response;
    }

    public Optional<SysUserEntity> resolveActiveUser(String accessToken) {
        DecodedJWT jwt = verifyToken(accessToken);
        UUID userId = UUID.fromString(jwt.getSubject());
        String jti = jwt.getId();
        return userSessionRepository.findActiveUserBySession(
                userId, jti, hashToken(accessToken), Instant.now());
    }

    public DecodedJWT verifyToken(String accessToken) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey());
            return JWT.require(algorithm).build().verify(accessToken);
        } catch (JWTVerificationException | IllegalArgumentException ex) {
            throw ex;
        }
    }

    private String createAccessToken(SysUserEntity user, String jti, Instant issuedAt, Instant expiresAt) {
        Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey());
        return JWT.create()
                .withSubject(user.getId().toString())
                .withClaim("account", user.getAccount())
                .withJWTId(jti)
                .withIssuedAt(Date.from(issuedAt))
                .withExpiresAt(Date.from(expiresAt))
                .sign(algorithm);
    }

    private String generateJti() {
        byte[] bytes = new byte[16];
        new SecureRandom().nextBytes(bytes);
        return HexFormat.of().formatHex(bytes);
    }

    public String hashToken(String accessToken) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hashed = digest.digest(accessToken.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(hashed);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalStateException("SHA-256 not available", ex);
        }
    }

    public UserResponse toUserResponse(SysUserEntity user) {
        UserResponse response = new UserResponse();
        response.setId(user.getId());
        response.setAccount(user.getAccount());
        response.setName(user.getName());
        response.setEmail(user.getEmail());
        response.setPhone(user.getPhone());
        return response;
    }
}
