package com.magistr.app.config.auth;

import com.magistr.app.model.AuthRefreshToken;
import com.magistr.app.model.User;
import com.magistr.app.repository.AuthRefreshTokenRepository;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
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.Clock;
import java.time.Instant;
import java.util.Base64;
import java.util.HexFormat;
import java.util.Optional;

@Service
public class RefreshTokenService {

    private static final int TOKEN_BYTES = 32;
    private static final int USER_AGENT_LIMIT = 512;
    private static final int IP_LIMIT = 64;

    private final SecureRandom secureRandom = new SecureRandom();
    private final AuthRefreshTokenRepository repository;
    private final JwtProperties properties;
    private final Clock clock;

    @Autowired
    public RefreshTokenService(AuthRefreshTokenRepository repository, JwtProperties properties) {
        this(repository, properties, Clock.systemUTC());
    }

    RefreshTokenService(AuthRefreshTokenRepository repository, JwtProperties properties, Clock clock) {
        this.repository = repository;
        this.properties = properties;
        this.clock = clock;
    }

    @Transactional
    public String createSession(User user, String tenant, String userAgent, String ipAddress) {
        String refreshToken = generateRawToken();
        AuthRefreshToken token = new AuthRefreshToken();
        Instant now = clock.instant();
        token.setUser(user);
        token.setTenant(tenant);
        token.setTokenHash(hashToken(refreshToken));
        token.setIssuedAt(now);
        token.setExpiresAt(now.plus(properties.getRefreshTtl()));
        token.setUserAgent(limit(userAgent, USER_AGENT_LIMIT));
        token.setIpAddress(limit(ipAddress, IP_LIMIT));
        repository.save(token);
        return refreshToken;
    }

    @Transactional
    public Optional<RefreshTokenRotation> rotate(String rawToken, String tenant, String userAgent, String ipAddress) {
        if (rawToken == null || rawToken.isBlank()) {
            return Optional.empty();
        }

        Instant now = clock.instant();
        String currentHash = hashToken(rawToken);
        Optional<AuthRefreshToken> storedOpt = repository.findByTokenHashForUpdate(currentHash);
        if (storedOpt.isEmpty()) {
            return Optional.empty();
        }

        AuthRefreshToken stored = storedOpt.get();
        if (!tenant.equalsIgnoreCase(stored.getTenant()) || !stored.isActive(now)) {
            return Optional.empty();
        }

        User user = stored.getUser();
        if (user.isArchivedRecord()) {
            stored.setRevokedAt(now);
            repository.save(stored);
            return Optional.empty();
        }

        String newRawToken = generateRawToken();
        String newHash = hashToken(newRawToken);
        stored.setRevokedAt(now);
        stored.setRotatedToTokenHash(newHash);
        repository.save(stored);

        AuthRefreshToken next = new AuthRefreshToken();
        next.setUser(user);
        next.setTenant(tenant);
        next.setTokenHash(newHash);
        next.setIssuedAt(now);
        next.setExpiresAt(now.plus(properties.getRefreshTtl()));
        next.setUserAgent(limit(userAgent, USER_AGENT_LIMIT));
        next.setIpAddress(limit(ipAddress, IP_LIMIT));
        repository.save(next);

        return Optional.of(new RefreshTokenRotation(user, newRawToken));
    }

    @Transactional
    public void revoke(String rawToken, String tenant) {
        if (rawToken == null || rawToken.isBlank()) {
            return;
        }

        repository.findByTokenHash(hashToken(rawToken))
                .filter(token -> tenant.equalsIgnoreCase(token.getTenant()))
                .filter(token -> token.getRevokedAt() == null)
                .ifPresent(token -> {
                    token.setRevokedAt(clock.instant());
                    repository.save(token);
                });
    }

    String hashToken(String rawToken) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(rawToken.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("SHA-256 недоступен", e);
        }
    }

    private String generateRawToken() {
        byte[] bytes = new byte[TOKEN_BYTES];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    private String limit(String value, int maxLength) {
        if (value == null || value.isBlank()) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.length() <= maxLength ? trimmed : trimmed.substring(0, maxLength);
    }
}
