package ru.kirillius.pf.sdn.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.kirillius.pf.sdn.config.JwtService;
import ru.kirillius.pf.sdn.entity.ApiToken;
import ru.kirillius.pf.sdn.entity.User;
import ru.kirillius.pf.sdn.repository.ApiTokenRepository;
import ru.kirillius.pf.sdn.repository.UserRepository;

import java.security.SecureRandom;
import java.util.HexFormat;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;
    private final ApiTokenRepository apiTokenRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;

    private static final int API_TOKEN_BYTES = 32;

    @Transactional
    public void createDefaultUserIfAbsent() {
        if (!userRepository.existsByUsername("admin")) {
            User user = new User();
            user.setUsername("admin");
            user.setPasswordHash(passwordEncoder.encode("admin"));
            userRepository.save(user);
        }
    }

    public Optional<String> login(String username, String rawPassword) {
        return userRepository.findByUsername(username)
                .filter(User::isEnabled)
                .filter(user -> passwordEncoder.matches(rawPassword, user.getPasswordHash()))
                .map(user -> jwtService.generateToken(user.getId()));
    }

    @Transactional
    public boolean changePassword(Long userId, String oldPassword, String newPassword) {
        return userRepository.findById(userId)
                .filter(user -> passwordEncoder.matches(oldPassword, user.getPasswordHash()))
                .map(user -> {
                    user.setPasswordHash(passwordEncoder.encode(newPassword));
                    userRepository.save(user);
                    return true;
                })
                .orElse(false);
    }

    public Optional<Long> validateToken(String token) {
        return jwtService.validateToken(token);
    }

    public void invalidateToken(String token) {
        // JWT is stateless — client just discards the token
    }

    @Transactional
    public String generateApiToken(Long userId, String name) {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[API_TOKEN_BYTES];
        random.nextBytes(bytes);
        String rawToken = HexFormat.of().formatHex(bytes);

        ApiToken apiToken = new ApiToken();
        apiToken.setName(name);
        apiToken.setTokenHash(passwordEncoder.encode(rawToken));
        apiToken.setUserId(userId);
        apiTokenRepository.save(apiToken);

        return apiToken.getId() + "." + rawToken;
    }

    public Optional<Long> validateApiToken(String rawToken) {
        for (ApiToken stored : apiTokenRepository.findByEnabledTrue()) {
            if (passwordEncoder.matches(rawToken, stored.getTokenHash())) {
                updateLastUsedAt(stored);
                return Optional.of(stored.getUserId());
            }
        }
        return Optional.empty();
    }

    public Optional<Long> resolveUserId(String token) {
        return validateToken(token)
                .or(() -> validateApiToken(token));
    }

    @Transactional
    public boolean deleteApiToken(Long tokenId, Long userId) {
        return apiTokenRepository.findByIdAndUserId(tokenId, userId)
                .map(token -> {
                    apiTokenRepository.delete(token);
                    return true;
                })
                .orElse(false);
    }

    public List<ApiToken> getUserApiTokens(Long userId) {
        return apiTokenRepository.findByUserId(userId);
    }

    private void updateLastUsedAt(ApiToken token) {
        token.setLastUsedAt(java.time.LocalDateTime.now());
        apiTokenRepository.save(token);
    }
}
