package org.raddatz.familienarchiv.auth;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.audit.AuditKind;
import org.raddatz.familienarchiv.audit.AuditService;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.user.UserService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthService {

    private final AuthenticationManager authenticationManager;
    private final UserService userService;
    private final AuditService auditService;
    private final LoginRateLimiter loginRateLimiter;
    private final SessionRevocationPort sessionRevocationPort;

    public LoginResult login(String email, String password, String ip, String ua) {
        try {
            loginRateLimiter.checkAndConsume(ip, email);
        } catch (DomainException ex) {
            auditService.log(AuditKind.LOGIN_RATE_LIMITED, null, null, Map.of(
                    "ip", ip,
                    "email", email));
            throw ex;
        }
        try {
            Authentication auth = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(email, password));

            AppUser user = userService.findByEmail(email);
            auditService.log(AuditKind.LOGIN_SUCCESS, user.getId(), null, Map.of(
                    "userId", user.getId().toString(),
                    "ip", ip,
                    "ua", truncateUa(ua)));
            loginRateLimiter.invalidateOnSuccess(ip, email);
            return new LoginResult(user, auth);
        } catch (AuthenticationException ex) {
            // Audit login failure — intentionally does NOT log the attempted password.
            // DaoAuthenticationProvider already runs a dummy BCrypt on unknown users to
            // equalise timing between "user not found" and "wrong password" paths.
            auditService.log(AuditKind.LOGIN_FAILED, null, null, Map.of(
                    "email", email,
                    "ip", ip,
                    "ua", truncateUa(ua)));
            throw DomainException.invalidCredentials();
        }
    }

    public int revokeOtherSessions(String currentSessionId, String principalName) {
        return sessionRevocationPort.revokeOtherSessions(currentSessionId, principalName);
    }

    public int revokeAllSessions(String principalName) {
        return sessionRevocationPort.revokeAllSessions(principalName);
    }

    public void logout(String email, String ip, String ua) {
        AppUser user = userService.findByEmail(email);
        auditService.log(AuditKind.LOGOUT, user.getId(), null, Map.of(
                "userId", user.getId().toString(),
                "ip", ip,
                "ua", truncateUa(ua)));
    }

    private static String truncateUa(String ua) {
        if (ua == null) return "";
        return ua.length() > 200 ? ua.substring(0, 200) : ua;
    }

    public record LoginResult(AppUser user, Authentication authentication) {}
}
