package com.pressing.modules.auth;

import com.pressing.common.exception.ApiException;
import com.pressing.domain.site.Site;
import com.pressing.domain.site.SiteRepository;
import com.pressing.domain.user.AppUser;
import com.pressing.domain.user.AppUserRepository;
import com.pressing.domain.user.PasswordResetToken;
import com.pressing.domain.user.PasswordResetTokenRepository;
import com.pressing.domain.user.RoleCode;
import com.pressing.modules.auth.dto.AuthResponse;
import com.pressing.modules.auth.dto.ChangeMyPasswordRequest;
import com.pressing.modules.auth.dto.ForgotPasswordRequest;
import com.pressing.modules.auth.dto.LoginRequest;
import com.pressing.modules.auth.dto.QuickResetPasswordRequest;
import com.pressing.modules.auth.dto.ResetPasswordRequest;
import com.pressing.modules.auth.dto.SiteSummaryDto;
import com.pressing.modules.auth.dto.UserProfileDto;
import com.pressing.security.UserPrincipal;
import com.pressing.security.jwt.JwtService;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AuthService {

    private final AuthenticationManager authenticationManager;
    private final AppUserRepository userRepository;
    private final SiteRepository siteRepository;
    private final PasswordResetTokenRepository resetTokenRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;
    private final MenuService menuService;

    public AuthService(
            AuthenticationManager authenticationManager,
            AppUserRepository userRepository,
            SiteRepository siteRepository,
            PasswordResetTokenRepository resetTokenRepository,
            PasswordEncoder passwordEncoder,
            JwtService jwtService,
            MenuService menuService
    ) {
        this.authenticationManager = authenticationManager;
        this.userRepository = userRepository;
        this.siteRepository = siteRepository;
        this.resetTokenRepository = resetTokenRepository;
        this.passwordEncoder = passwordEncoder;
        this.jwtService = jwtService;
        this.menuService = menuService;
    }

    @Transactional
    public AuthResponse login(LoginRequest request) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.login(), request.password()));
        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
        AppUser user = userRepository.findById(principal.getId()).orElseThrow();
        return buildResponse(user, null);
    }

    @Transactional
    public AuthResponse selectSite(AppUser current, Long siteId) {
        AppUser user = userRepository.findById(current.getId()).orElseThrow();
        Site site = siteRepository.findById(siteId).orElseThrow(() -> ApiException.notFound("Site introuvable"));
        boolean admin = hasRole(user, RoleCode.ADMIN);
        boolean allowed = admin || user.getSites().stream().anyMatch(s -> s.getId().equals(siteId));
        if (!allowed) {
            throw ApiException.forbidden("Vous n'avez pas accès à ce site");
        }
        return buildResponse(user, site);
    }

    @Transactional
    public AuthResponse me(AppUser current, Long siteId) {
        AppUser user = userRepository.findById(current.getId()).orElseThrow();
        Site site = siteId == null ? null : siteRepository.findById(siteId).orElse(null);
        return buildResponse(user, site);
    }

    @Transactional
    public Map<String, String> forgotPassword(ForgotPasswordRequest request) {
        AppUser user = userRepository.findByUsernameIgnoreCaseOrEmailIgnoreCase(request.login(), request.login())
                .orElseThrow(() -> ApiException.notFound("Aucun compte correspondant"));
        PasswordResetToken token = new PasswordResetToken();
        token.setUserId(user.getId());
        token.setToken(UUID.randomUUID().toString());
        token.setExpiresAt(Instant.now().plus(2, ChronoUnit.HOURS));
        token.setUsed(false);
        resetTokenRepository.save(token);
        Map<String, String> payload = new HashMap<>();
        payload.put("message", "Un jeton de réinitialisation a été généré (mode développement).");
        payload.put("resetToken", token.getToken());
        return payload;
    }

@Transactional
    public void changeMyPassword(AppUser currentUser, ChangeMyPasswordRequest request) {
        AppUser user = userRepository.findById(currentUser.getId())
                .orElseThrow(() -> ApiException.notFound("Utilisateur introuvable"));

        // 1. Vérifier que l'ancien mot de passe est correct
        if (!passwordEncoder.matches(request.oldPassword(), user.getPassword())) {
            throw ApiException.badRequest("L'ancien mot de passe est incorrect");
        }

        // 2. Enregistrer le nouveau mot de passe
        user.setPassword(passwordEncoder.encode(request.newPassword()));
        userRepository.save(user);
    }

    @Transactional
    public void resetPassword(ResetPasswordRequest request) {
        PasswordResetToken token = resetTokenRepository.findByToken(request.token())
                .orElseThrow(() -> ApiException.badRequest("Jeton invalide"));
        if (token.isUsed() || token.getExpiresAt().isBefore(Instant.now())) {
            throw ApiException.badRequest("Jeton expiré ou déjà utilisé");
        }
        AppUser user = userRepository.findById(token.getUserId()).orElseThrow(() -> ApiException.notFound("Utilisateur introuvable"));
        user.setPassword(passwordEncoder.encode(request.newPassword()));
        token.setUsed(true);
        userRepository.save(user);
        resetTokenRepository.save(token);
    }

    private AuthResponse buildResponse(AppUser user, Site selectedSite) {
        List<Site> sites = hasRole(user, RoleCode.ADMIN)
                ? siteRepository.findAll()
                : user.getSites().stream().toList();
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", user.getId());
        claims.put("roles", user.getRoles().stream().map(r -> r.getCode().name()).toList());
        if (selectedSite != null) {
            claims.put("siteId", selectedSite.getId());
        }
        String token = jwtService.generateToken(user.getUsername(), claims);
        return new AuthResponse(
                token,
                toUser(user),
                selectedSite == null ? null : toSite(selectedSite),
                sites.stream().map(this::toSite).toList(),
                menuService.menusFor(user, selectedSite)
        );
    }

    private UserProfileDto toUser(AppUser user) {
        Set<String> roles = user.getRoles().stream().map(r -> r.getCode().name()).collect(Collectors.toSet());
        return new UserProfileDto(user.getId(), user.getUsername(), user.getEmail(), user.getFirstName(), user.getLastName(), user.getPhone(), roles);
    }

   

    private SiteSummaryDto toSite(Site site) {
    return new SiteSummaryDto(
        site.getId(),
        site.getName(),
        site.getCode(),
        site.getActivityDomain(),
        site.isActive()
    );
}

    private boolean hasRole(AppUser user, RoleCode code) {
        return user.getRoles().stream().anyMatch(r -> r.getCode() == code);
    }


    @Transactional
    public void quickResetPassword(QuickResetPasswordRequest request) {
        AppUser user = userRepository.findById(request.userId())
                .orElseThrow(() -> ApiException.notFound("Utilisateur introuvable"));
        
        // Encodage direct du nouveau mot de passe (ex: "0000")
        user.setPassword(passwordEncoder.encode(request.newPassword()));
        userRepository.save(user);
    }
}
