package com.walkguide.service;

import com.walkguide.dto.request.LoginRequest;
import com.walkguide.dto.request.RegisterRequest;
import com.walkguide.dto.response.AuthDataResponse;
import com.walkguide.entity.HardwareShortcut;
import com.walkguide.entity.RefreshToken;
import com.walkguide.entity.User;
import com.walkguide.entity.UserSettings;
import com.walkguide.entity.VoiceCommandConfig;
import com.walkguide.enums.ActivityLogType;
import com.walkguide.enums.HardwareShortcutKey;
import com.walkguide.enums.VoiceCommandKey;
import com.walkguide.repository.HardwareShortcutRepository;
import com.walkguide.repository.RefreshTokenRepository;
import com.walkguide.repository.UserRepository;
import com.walkguide.repository.UserSettingsRepository;
import com.walkguide.repository.VoiceCommandConfigRepository;
import com.walkguide.security.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserSettingsRepository userSettingsRepository;
    private final HardwareShortcutRepository hardwareShortcutRepository;
    private final VoiceCommandConfigRepository voiceCommandConfigRepository;
    private final ActivityLogService activityLogService;
    private final JwtUtil jwtUtil;
    private final PasswordEncoder passwordEncoder;

    private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int ID_LENGTH = 12;

    private static final Map<VoiceCommandKey, String> DEFAULT_PHRASES = Map.ofEntries(
        Map.entry(VoiceCommandKey.OPEN_WALKGUIDE,    "buka walkguide"),
        Map.entry(VoiceCommandKey.START_WALKGUIDE,   "mulai walkguide"),
        Map.entry(VoiceCommandKey.STOP_WALKGUIDE,    "hentikan walkguide"),
        Map.entry(VoiceCommandKey.CALL_GUARDIAN,     "telepon guardian"),
        Map.entry(VoiceCommandKey.OPEN_NOTIFICATION, "buka notifikasi"),
        Map.entry(VoiceCommandKey.READ_ALL_NOTIF,    "baca notifikasi"),
        Map.entry(VoiceCommandKey.OPEN_SOS,          "buka sos"),
        Map.entry(VoiceCommandKey.SEND_SOS,          "kirim sos"),
        Map.entry(VoiceCommandKey.WHERE_AM_I,        "di mana saya"),
        Map.entry(VoiceCommandKey.OPEN_ACTIVITY,     "buka aktivitas"),
        Map.entry(VoiceCommandKey.OPEN_NAVIGATION,   "buka navigasi"),
        Map.entry(VoiceCommandKey.OPEN_SETTINGS,     "buka pengaturan"),
        Map.entry(VoiceCommandKey.REPEAT_LAST,       "ulangi"),
        Map.entry(VoiceCommandKey.STOP_TTS,          "berhenti")
    );

    @Transactional
    public AuthDataResponse register(RegisterRequest req) {
        if (userRepository.existsByEmail(req.getEmail())) {
            throw new RuntimeException("Email sudah terdaftar");
        }

        String role = "GUARDIAN".equalsIgnoreCase(req.getRole())
                ? "ROLE_GUARDIAN" : "ROLE_USER";

        String uniqueUserId = null;
        if ("ROLE_USER".equals(role)) {
            uniqueUserId = generateUniqueUserId();
        }

        User user = User.builder()
                .email(req.getEmail())
                .password(passwordEncoder.encode(req.getPassword()))
                .role(role)
                .displayName(req.getDisplayName())
                .uniqueUserId(uniqueUserId)
                .build();

        user = userRepository.save(user);

        if ("ROLE_USER".equals(role)) {
            // 1. Default settings
            userSettingsRepository.save(UserSettings.builder()
                    .userId(user.getId())
                    .build());

            // 2. Default hardware shortcuts
            for (HardwareShortcutKey key : HardwareShortcutKey.values()) {
                hardwareShortcutRepository.save(HardwareShortcut.builder()
                        .userId(user.getId())
                        .shortcutKey(key)
                        .enabled(true)
                        .build());
            }

            // 3. Default voice commands
            for (VoiceCommandKey key : VoiceCommandKey.values()) {
                voiceCommandConfigRepository.save(VoiceCommandConfig.builder()
                        .userId(user.getId())
                        .commandKey(key)
                        .triggerPhrase(DEFAULT_PHRASES.getOrDefault(key, key.name().toLowerCase()))
                        .enabled(true)
                        .build());
            }
        }

        return buildAuthResponse(user);
    }

    @Transactional
    public AuthDataResponse login(LoginRequest req) {
        User user = userRepository.findByEmail(req.getEmail())
                .orElseThrow(() -> new RuntimeException("Email tidak terdaftar"));

        if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
            throw new RuntimeException("Password salah");
        }

        refreshTokenRepository.deleteByUserId(user.getId());
        activityLogService.createLog(user, ActivityLogType.LOGIN, "User login", null);

        return buildAuthResponse(user);
    }

    @Transactional
    public AuthDataResponse refreshToken(String token) {
        RefreshToken refreshToken = refreshTokenRepository.findByToken(token)
                .orElseThrow(() -> new RuntimeException("Refresh token tidak valid"));

        if (refreshToken.getExpiresAt().isBefore(LocalDateTime.now())) {
            refreshTokenRepository.delete(refreshToken);
            throw new RuntimeException("Refresh token sudah kadaluarsa, silakan login ulang");
        }

        User user = userRepository.findById(refreshToken.getUserId())
                .orElseThrow(() -> new RuntimeException("User tidak ditemukan"));

        String newAccessToken = jwtUtil.generateAccessToken(
                user.getEmail(), user.getRole(), user.getId());

        return AuthDataResponse.builder()
                .accessToken(newAccessToken)
                .refreshToken(token)
                .role(user.getRole())
                .userId(user.getId())
                .displayName(user.getDisplayName())
                .uniqueUserId(user.getUniqueUserId())
                .build();
    }

    @Transactional
    public void logout(Long userId) {
        refreshTokenRepository.deleteByUserId(userId);
        userRepository.findById(userId).ifPresent(user ->
                activityLogService.createLog(user, ActivityLogType.LOGOUT, "User logout", null));
    }

    @Transactional
    public void updateFcmToken(Long userId, String fcmToken) {
        userRepository.findById(userId).ifPresent(user -> {
            user.setFcmToken(fcmToken);
            userRepository.save(user);
        });
    }

    private AuthDataResponse buildAuthResponse(User user) {
        String accessToken = jwtUtil.generateAccessToken(
                user.getEmail(), user.getRole(), user.getId());
        String refreshTokenStr = jwtUtil.generateRefreshToken();

        refreshTokenRepository.save(RefreshToken.builder()
                .userId(user.getId())
                .token(refreshTokenStr)
                .expiresAt(LocalDateTime.now().plusDays(30))
                .build());

        return AuthDataResponse.builder()
                .accessToken(accessToken)
                .refreshToken(refreshTokenStr)
                .role(user.getRole())
                .userId(user.getId())
                .displayName(user.getDisplayName())
                .uniqueUserId(user.getUniqueUserId())
                .build();
    }

    private String generateUniqueUserId() {
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(ID_LENGTH);
        String candidate;
        do {
            sb.setLength(0);
            for (int i = 0; i < ID_LENGTH; i++) {
                sb.append(CHARS.charAt(random.nextInt(CHARS.length())));
            }
            candidate = sb.toString();
        } while (userRepository.findByUniqueUserId(candidate).isPresent());
        return candidate;
    }
}