package net.shyshkin.study.fullstack.supportportal.backend.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.shyshkin.study.fullstack.supportportal.backend.domain.Professor;
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.ProfessorDto;
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.EmailExistsException;
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.NotAnImageFileException;
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.ProfessorNotFoundException;
import net.shyshkin.study.fullstack.supportportal.backend.mapper.ProfessorMapper;
import net.shyshkin.study.fullstack.supportportal.backend.repository.ProfessorRepository;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.RequestEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;


import static net.shyshkin.study.fullstack.supportportal.backend.constant.FileConstant.*;


import static org.springframework.http.MediaType.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class ProfessorServiceImpl implements ProfessorService {

    public static final String EMAIL_NOT_FOUND_MSG = "Professor with email `%s` not found";
    public static final String EMAIL_EXISTS_MSG = "Professor with email `%s` is already registered";
    public static final String PROFESSOR_NOT_FOUND_MSG = "Professor not found";

    private final ProfessorRepository professorRepository;
    private final PasswordEncoder passwordEncoder;
    private final EmailService emailService;
    private final ProfessorMapper professorMapper;
    private final ProfileImageService profileImageService;
    private final RestTemplateBuilder restTemplateBuilder;


    private RestTemplate restTemplate;


    @PostConstruct
    void init() {
        restTemplate = restTemplateBuilder
                .rootUri(TEMP_PROFILE_IMAGE_BASE_URL)
                .build();
    }

    @Override
    @Transactional
    public Professor register(String firstName, String lastName, String email, String department, String position) {
        ProfessorDto professorDto = ProfessorDto.builder()
                .firstName(firstName)
                .lastName(lastName)
                .email(email)
                .department(department)
                .position(position)
                .build();

        return addNewProfessor(professorDto);
    }

    private String generateDefaultProfileImageUrl(UUID professorId) {
        return ServletUriComponentsBuilder.fromCurrentContextPath()
                .path(String.format(DEFAULT_USER_IMAGE_URI_PATTERN, professorId))
                .toUriString();
    }

    private String generateProfileImageUrl(UUID professorId) {
        return ServletUriComponentsBuilder.fromCurrentContextPath()
                .path(String.format(DEFAULT_USER_IMAGE_URI_PATTERN, professorId))
                .pathSegment(USER_IMAGE_FILENAME)
                .toUriString();
    }

    @Override
    public Page<Professor> findAll(Pageable pageable) {
        return professorRepository.findAll(pageable);
    }

    @Override
    public Professor findByEmail(String email) {
        return professorRepository
                .findByEmail(email)
                .orElseThrow(() -> new EmailExistsException(String.format(EMAIL_NOT_FOUND_MSG, email)));
    }

    @Override
    public Professor findByProfessorId(UUID professorId) {
        return professorRepository
                .findByProfessorId(professorId)
                .orElseThrow(() -> new ProfessorNotFoundException(PROFESSOR_NOT_FOUND_MSG));
    }

   

    private void saveProfileImage(Professor professor, MultipartFile profileImage) {
        if (profileImage == null) return;

        if (!List.of(IMAGE_JPEG_VALUE, IMAGE_GIF_VALUE, IMAGE_PNG_VALUE).contains(profileImage.getContentType())) {
            throw new NotAnImageFileException(profileImage.getOriginalFilename() + " is not an image file. Please upload an image");
        }

        String imageUrl = profileImageService.persistProfileImage(professor.getProfessorId(), profileImage, USER_IMAGE_FILENAME);

        if (imageUrl == null)
            imageUrl = generateProfileImageUrl(professor.getProfessorId());

        professor.setProfileImageUrl(imageUrl);
        professorRepository.save(professor);
    }

    private void clearProfessorStorage(Professor professor) {
        profileImageService.clearUserStorage(professor.getProfessorId());
    }

    private UUID generateUuid() {
        return UUID.randomUUID();
    }

    @Override
    public Professor addNewProfessor(ProfessorDto professorDto) {

        validateNewEmail(professorDto.getEmail());

        Professor professor = professorMapper.toEntity(professorDto);

        // Set a unique identifier for the professor
        professor.setProfessorId(generateUuid());
        // professor.setProfessorId(UUID.randomUUID()); // Set a unique identifier for the professor
        professor.setJoinDate(LocalDateTime.now()); // Set join date if not provided
        professor.setProfileImageUrl(generateDefaultProfileImageUrl(professor.getProfessorId()));

        professorRepository.save(professor);

        // saveProfileImage(professor, professorDto.getProfileImage());

        return professor;
    }

    @Override
    public Professor updateProfessor(UUID professorId, ProfessorDto professorDto) {

        Professor professor = professorRepository.findByProfessorId(professorId)
                .orElseThrow(() -> new RuntimeException("Professor not found with id: " + professorId));

        validateUpdateEmail(professorId,professorDto.getEmail());

        professor.setFirstName(professorDto.getFirstName());
        professor.setLastName(professorDto.getLastName());
        professor.setEmail(professorDto.getEmail());
        professor.setDepartment(professorDto.getDepartment());
        professor.setPosition(professorDto.getPosition());
        professor.setOfficeLocation(professorDto.getOfficeLocation());
        professor.setStatus(professorDto.getStatus());
        professor.setJoinDate(professorDto.getJoinDate()); // Update join date if provided

        professorRepository.save(professor);

        saveProfileImage(professor, professorDto.getProfileImage());

        return professor;
    }

    @Override
    public void deleteProfessor(UUID professorId) {
        Professor professorToBeDeleted = professorRepository
                .findByProfessorId(professorId)
                .orElseThrow(() -> new ProfessorNotFoundException(PROFESSOR_NOT_FOUND_MSG));

        clearProfessorStorage(professorToBeDeleted);
        professorRepository.delete(professorToBeDeleted);
    }

    @Override
    public Professor updateProfileImage(UUID professorId, MultipartFile profileImage) {
        Professor professor = findByProfessorId(professorId);
        saveProfileImage(professor, profileImage);
        return professor;
    }

    @Override
    public byte[] getImageByProfessorId(UUID professorId, String filename) {
        if (!professorRepository.existsByProfessorId(professorId)) {
            throw new ProfessorNotFoundException(PROFESSOR_NOT_FOUND_MSG);
        }
        return profileImageService.retrieveProfileImage(professorId, filename);
    }

    @Override
    public byte[] getDefaultProfileImage(UUID professorId) {
        if (!professorRepository.existsByProfessorId(professorId)) {
            throw new ProfessorNotFoundException(PROFESSOR_NOT_FOUND_MSG);
        }

        RequestEntity<Void> requestEntity = RequestEntity
                .get("/{professorId}", professorId)
                .accept(IMAGE_JPEG)
                .build();
        var responseEntity = restTemplate.exchange(requestEntity, new ParameterizedTypeReference<byte[]>() {});
        return responseEntity.getBody();
    }

    private void validateNewEmail(String email) {
        if (professorRepository.existsByEmail(email)) {
            throw new EmailExistsException(String.format(EMAIL_EXISTS_MSG, email));
        }
    }

    private Professor validateUpdateEmail(UUID professorId, String email) {
        Objects.requireNonNull(professorId);

        Professor currentProfessor = findByProfessorId(professorId);

        if (!Objects.equals(currentProfessor.getEmail(), email) && professorRepository.existsByEmail(email)) {
            throw new EmailExistsException(String.format(EMAIL_EXISTS_MSG, email));
        }

        return currentProfessor;
    }
}
