package com.petshop.backend.service;

import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.user.UserRequest;
import com.petshop.backend.dto.user.UserResponse;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.StoreRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.util.AuthenticationHelper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CONFLICT;

@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final StoreRepository storeRepository;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, StoreRepository storeRepository) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.storeRepository = storeRepository;
    }

    public Page<UserResponse> getAllUsers(String query, String role, Pageable pageable) {
        User.Role parsedRole = parseRole(role);
        Page<User> users;
        boolean hasQuery = query != null && !query.trim().isEmpty();
        if (hasQuery && parsedRole != null) {
            users = userRepository.searchUsersByRole(query, parsedRole, pageable);
        } else if (hasQuery) {
            users = userRepository.searchUsers(query, pageable);
        } else if (parsedRole != null) {
            users = userRepository.findByRole(parsedRole, pageable);
        } else {
            users = userRepository.findAll(pageable);
        }
        return users.map(this::mapToResponse);
    }

    public UserResponse getUserById(Long id) {
        return getUserById(id, null);
    }

    @Transactional(readOnly = true)
    public UserResponse getUserById(Long id, User.Role requiredRole) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
        requireRoleOrNotFound(user, requiredRole, id);
        return mapToResponse(user);
    }

    @Transactional
    public UserResponse createUser(UserRequest request) {
        return createUser(request, null);
    }

    @Transactional
    public UserResponse createUser(UserRequest request, User.Role requiredRole) {
        requireRequestedRole(request.getRole(), requiredRole);

        User user = new User();
        user.setUsername(trimToNull(request.getUsername()));
        if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
            user.setPassword(passwordEncoder.encode(request.getPassword()));
        }
        user.setFirstName(request.getFirstName());
        user.setLastName(request.getLastName());
        user.setFullName(request.getFullName());
        user.setEmail(request.getEmail());
        user.setPhone(trimToNull(request.getPhone()));
        user.setRole(request.getRole());
        user.setStaffRole(trimToNull(request.getStaffRole()));
        user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
        user.setActive(request.getActive() != null ? request.getActive() : true);
        if (request.getLoyaltyPoints() != null) {
            user.setLoyaltyPoints(request.getLoyaltyPoints());
        }

        validateUniquePhone(user.getPhone(), null);

        user = userRepository.save(user);

        return mapToResponse(user);
    }

    @Transactional
    public UserResponse updateUser(Long id, UserRequest request) {
        return updateUser(id, request, null);
    }

    @Transactional
    public UserResponse updateUser(Long id, UserRequest request, User.Role requiredRole) {
        User user = userRepository.findByIdForUpdate(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));

        requireRoleOrNotFound(user, requiredRole, id);
        requireRequestedRole(request.getRole(), requiredRole);
        requireAdminMutationAllowed(user, request.getRole());

        boolean invalidateToken =
                !Objects.equals(user.getUsername(), request.getUsername())
                        || user.getRole() != request.getRole()
                        || !user.getActive().equals(request.getActive() != null ? request.getActive() : true);

        user.setUsername(trimToNull(request.getUsername()));
        if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
            user.setPassword(passwordEncoder.encode(request.getPassword()));
            invalidateToken = true;
        }
        user.setFirstName(request.getFirstName());
        user.setLastName(request.getLastName());
        user.setFullName(request.getFullName());
        user.setEmail(request.getEmail());
        String phone = trimToNull(request.getPhone());
        if (!Objects.equals(user.getPhone(), phone)) {
            validateUniquePhone(phone, user.getId());
        }
        user.setPhone(phone);
        user.setRole(request.getRole());
        user.setStaffRole(trimToNull(request.getStaffRole()));
        user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
        user.setActive(request.getActive() != null ? request.getActive() : true);
        if (request.getLoyaltyPoints() != null) {
            user.setLoyaltyPoints(request.getLoyaltyPoints());
        }
        if (invalidateToken) {
            user.setTokenVersion(user.getTokenVersion() + 1);
        }

        user = userRepository.save(user);
        return mapToResponse(user);
    }

    @Transactional
    public void deleteUser(Long id) {
        deleteUser(id, null);
    }

    @Transactional
    public void deleteUser(Long id, User.Role requiredRole) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));

        requireRoleOrNotFound(user, requiredRole, id);
        requireAdminDeletionAllowed(user);

        userRepository.deleteById(id);
    }

    @Transactional
    public void bulkDeleteUsers(BulkDeleteRequest request) {
        bulkDeleteUsers(request, null);
    }

    @Transactional
    public void bulkDeleteUsers(BulkDeleteRequest request, User.Role requiredRole) {
        if (request.getIds() == null || request.getIds().isEmpty()) {
            throw new ResponseStatusException(BAD_REQUEST, "IDs list cannot be empty");
        }

        Set<Long> requestedIds = new HashSet<>(request.getIds());
        ArrayList<User> usersToDelete = new ArrayList<>();
        userRepository.findAllById(requestedIds).forEach(usersToDelete::add);

        if (usersToDelete.size() != requestedIds.size()) {
            throw new ResourceNotFoundException("One or more users not found for bulk delete");
        }

        requireRoleOrNotFound(usersToDelete, requiredRole);
        requireAdminDeletionAllowed(usersToDelete);

        userRepository.deleteAllById(request.getIds());
    }

    private UserResponse mapToResponse(User user) {
        UserResponse response = new UserResponse();
        response.setId(user.getId());
        response.setUsername(user.getUsername());
        response.setFirstName(user.getFirstName());
        response.setLastName(user.getLastName());
        response.setFullName(user.getFullName());
        response.setEmail(user.getEmail());
        response.setPhone(user.getPhone());
        response.setRole(user.getRole().toString());
        response.setStaffRole(user.getStaffRole());
        response.setPrimaryStoreId(user.getPrimaryStore() != null ? user.getPrimaryStore().getStoreId() : null);
        response.setLoyaltyPoints(user.getLoyaltyPoints());
        response.setActive(user.getActive());
        response.setAvatarUrl(user.getAvatarUrl() != null ? "/api/v1/users/" + user.getId() + "/avatar/file" : null);
        response.setCreatedAt(user.getCreatedAt());
        response.setUpdatedAt(user.getUpdatedAt());
        return response;
    }

    private StoreLocation resolveStore(Long storeId) {
        if (storeId == null) return null;
        return storeRepository.findById(storeId)
                .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId));
    }

    private void requireRoleOrNotFound(User user, User.Role requiredRole, Long id) {
        if (requiredRole != null && user.getRole() != requiredRole) {
            throw new ResourceNotFoundException("User not found with id: " + id);
        }
    }

    private void requireRoleOrNotFound(Collection<User> users, User.Role requiredRole) {
        if (requiredRole == null) {
            return;
        }
        for (User user : users) {
            if (user.getRole() != requiredRole) {
                throw new ResourceNotFoundException("User not found with id: " + user.getId());
            }
        }
    }

    private void requireRequestedRole(User.Role requestedRole, User.Role requiredRole) {
        if (requiredRole != null && requestedRole != requiredRole) {
            throw new AccessDeniedException("Target user must have role " + requiredRole.name());
        }
    }

    private void requireAdminMutationAllowed(User target, User.Role requestedRole) {
        User actor = AuthenticationHelper.getAuthenticatedUser(userRepository);
        if ((target.getRole() == User.Role.ADMIN && !actor.getId().equals(target.getId()))
                || (requestedRole == User.Role.ADMIN && !actor.getId().equals(target.getId()))) {
            throw new AccessDeniedException("Admins cannot modify other admin accounts");
        }
    }

    private void requireAdminDeletionAllowed(User target) {
        if (target.getRole() == User.Role.ADMIN) {
            throw new AccessDeniedException("Admins cannot delete admin accounts");
        }
    }

    private void requireAdminDeletionAllowed(Collection<User> targets) {
        for (User target : targets) {
            if (target.getRole() == User.Role.ADMIN) {
                throw new AccessDeniedException("Admins cannot delete admin accounts");
            }
        }
    }

    private void validateUniquePhone(String phone, Long currentUserId) {
        if (phone == null || phone.isBlank()) {
            return;
        }
        userRepository.findByPhone(phone)
                .filter(existing -> !existing.getId().equals(currentUserId))
                .ifPresent(existing -> {
                    throw new ResponseStatusException(CONFLICT, "Phone already exists");
                });
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private User.Role parseRole(String role) {
        String normalizedRole = trimToNull(role);
        if (normalizedRole == null) {
            return null;
        }
        try {
            return User.Role.valueOf(normalizedRole.toUpperCase(Locale.ROOT));
        } catch (IllegalArgumentException ex) {
            throw new ResponseStatusException(BAD_REQUEST, "Invalid value for parameter: role");
        }
    }
}
