package de.votian.services.service;

import de.votian.services.dto.AuthResponse;
import de.votian.services.dto.UserSessionDto;
import de.votian.services.entity.AppRight;
import de.votian.services.entity.Courier;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Employee;
import de.votian.services.entity.EmployeeCostCenter;
import de.votian.services.entity.EmployeeRights;
import de.votian.services.entity.User;
import de.votian.services.repository.AppRightRepository;
import de.votian.services.repository.CourierRepository;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.EmployeeCostCenterRepository;
import de.votian.services.repository.EmployeeRepository;
import de.votian.services.repository.EmployeeRightsRepository;
import de.votian.services.repository.UserRepository;
import de.votian.services.security.AuthSessionService;
import de.votian.services.security.TotpUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Service
@Transactional
public class AuthService {

    private static final int LEGACY_RIGHT_LENGTH = 50;

    private final UserRepository userRepository;
    private final EmployeeRepository employeeRepository;
    private final CostCenterRepository costCenterRepository;
    private final CourierRepository courierRepository;
    private final EmployeeCostCenterRepository employeeCostCenterRepository;
    private final EmployeeRightsRepository employeeRightsRepository;
    private final AppRightRepository appRightRepository;
    private final AuthSessionService authSessionService;

    public AuthService(UserRepository userRepository,
                       EmployeeRepository employeeRepository,
                       CostCenterRepository costCenterRepository,
                       CourierRepository courierRepository,
                       EmployeeCostCenterRepository employeeCostCenterRepository,
                       EmployeeRightsRepository employeeRightsRepository,
                       AppRightRepository appRightRepository,
                       AuthSessionService authSessionService) {
        this.userRepository = userRepository;
        this.employeeRepository = employeeRepository;
        this.costCenterRepository = costCenterRepository;
        this.courierRepository = courierRepository;
        this.employeeCostCenterRepository = employeeCostCenterRepository;
        this.employeeRightsRepository = employeeRightsRepository;
        this.appRightRepository = appRightRepository;
        this.authSessionService = authSessionService;
    }

    public Optional<AuthResponse> login(String account, String password) {
        return userRepository.authenticate(account, password)
                .map(this::prepareLoginResponse);
    }

    public Optional<AuthResponse> verifyTotp(String pendingToken, String code) {
        return authSessionService.consumePendingTotpSession(pendingToken)
                .filter(user -> {
                    User dbUser = userRepository.findById(Objects.requireNonNull(user.getUserId())).orElse(null);
                    return dbUser != null && TotpUtil.verifyCode(dbUser.getTotpSecret(), code);
                })
                .map(user -> {
                    userRepository.findById(Objects.requireNonNull(user.getUserId())).ifPresent(dbUser ->
                            dbUser.setTotpSessionKey(TotpUtil.randomSessionKey()));
                    AuthResponse response = new AuthResponse();
                    response.setAuthenticated(true);
                    response.setRequiresTotp(false);
                    response.setAuthToken(authSessionService.createAuthenticatedSession(user));
                    response.setUser(user);
                    return response;
                });
    }

    public boolean changePassword(Long userId, String currentPassword, String newPassword) {
        Optional<User> user = userRepository.authenticateById(userId, currentPassword);
        if (user.isEmpty()) {
            return false;
        }
        return userRepository.updatePasswordWithMysqlFunction(userId, newPassword) > 0;
    }

    public Optional<UserSessionDto> resolveSession(String authToken) {
        return authSessionService.resolveAuthenticatedSession(authToken);
    }

    public void logout(String authToken) {
        authSessionService.invalidateAuthenticatedSession(authToken);
    }

    private AuthResponse prepareLoginResponse(User user) {
        UserSessionDto sessionUser = createSessionUser(user);
        AuthResponse response = new AuthResponse();
        response.setUser(sessionUser);

        boolean requiresTotp = user.getTotpSecret() != null
                && !user.getTotpSecret().isBlank()
                && "1".equals(user.getTotpActivated());

        if (requiresTotp) {
            response.setAuthenticated(false);
            response.setRequiresTotp(true);
            response.setPendingToken(authSessionService.createPendingTotpSession(sessionUser));
        } else {
            response.setAuthenticated(true);
            response.setRequiresTotp(false);
            response.setAuthToken(authSessionService.createAuthenticatedSession(sessionUser));
        }

        return response;
    }

    private UserSessionDto createSessionUser(User user) {
        UserSessionDto dto = new UserSessionDto();
        dto.setUserId(user.getId());
        dto.setHeadquartersId(user.getHeadquartersId());
        dto.setUserType(user.getType());
        dto.setName(user.getName());
        dto.setFirstname(user.getFirstname());

        employeeRepository.findByUserId(user.getId()).ifPresent(emp -> applyEmployeeData(dto, user, emp));

        if (user.getType() != null && user.getType() == 3) {
            courierRepository.findByUserId(user.getId())
                    .map(Courier::getId)
                    .ifPresent(dto::setCourierId);
        }

        return dto;
    }

    private void applyEmployeeData(UserSessionDto dto, User user, Employee employee) {
        if (user.getType() != null && (user.getType() == 1 || user.getType() == 2 || user.getType() == 4)) {
            dto.setEmployeeId(employee.getId());
        }
        dto.setLegacyRights(normalizeLegacyRights(employee.getRights()));
        applyMatrixRights(dto, employee);

        if (user.getType() == null || user.getType() != 2) {
            return;
        }

        dto.setCostCenterId(employee.getCostCenterId());
        if (employee.getCostCenterId() != null) {
            costCenterRepository.findById(Objects.requireNonNull(employee.getCostCenterId()))
                    .map(CostCenter::getCustomerId)
                    .ifPresent(dto::setCustomerId);
        }

        LinkedHashSet<Long> accessibleCostCenterIds = new LinkedHashSet<>();
        if (employee.getCostCenterId() != null) {
            accessibleCostCenterIds.add(employee.getCostCenterId());
        }
        for (EmployeeCostCenter employeeCostCenter :
                employeeCostCenterRepository.findByEmployeeIdOrderByCostCenterIdAsc(employee.getId())) {
            if (employeeCostCenter.getCostCenterId() != null) {
                accessibleCostCenterIds.add(employeeCostCenter.getCostCenterId());
            }
        }
        dto.setAccessibleCostCenterIds(new ArrayList<>(accessibleCostCenterIds));
    }

    private void applyMatrixRights(UserSessionDto dto, Employee employee) {
        if (employee == null || employee.getId() == null) {
            return;
        }

        LinkedHashSet<Long> rightIds = new LinkedHashSet<>();
        for (EmployeeRights right : employeeRightsRepository.findByEmployeeId(employee.getId())) {
            if (right.getRightId() != null) {
                rightIds.add(right.getRightId());
            }
        }
        dto.setRightIds(new ArrayList<>(rightIds));

        if (rightIds.isEmpty()) {
            dto.setRightNames(List.of());
            return;
        }

        LinkedHashMap<Long, String> rightNamesById = new LinkedHashMap<>();
        for (AppRight appRight : appRightRepository.findAllByOrderByIdAsc()) {
            rightNamesById.put(appRight.getId(), appRight.getName());
        }

        List<String> rightNames = new ArrayList<>();
        for (Long rightId : rightIds) {
            String name = rightNamesById.get(rightId);
            if (name != null && !name.isBlank()) {
                rightNames.add(name);
            }
        }
        dto.setRightNames(rightNames);
    }

    private String normalizeLegacyRights(String rawRights) {
        if (rawRights == null || rawRights.isBlank()) {
            return null;
        }

        String trimmed = rawRights.trim();
        if (trimmed.length() >= LEGACY_RIGHT_LENGTH) {
            return trimmed.substring(0, LEGACY_RIGHT_LENGTH);
        }

        StringBuilder builder = new StringBuilder(trimmed);
        while (builder.length() < LEGACY_RIGHT_LENGTH) {
            builder.append('0');
        }
        return builder.toString();
    }
}
