package ru.soune.nocopy.service.user;

import jakarta.persistence.EntityNotFoundException;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.register.AccountType;
import ru.soune.nocopy.dto.register.ChangePasswordRequest;
import ru.soune.nocopy.dto.register.RegRequest;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.dto.user.UserDTO;
import ru.soune.nocopy.dto.user.UserRequest;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.user.Permission;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.CompanyAlreadyExist;
import ru.soune.nocopy.exception.InvalidUserEmail;
import ru.soune.nocopy.exception.NotValidationPasswordException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.CompanyRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.FileStatsService;
import ru.soune.nocopy.service.tariff.TariffInfoService;

import java.util.List;
import java.util.Optional;

@Service
@AllArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    private final FileStatsService fileStatsService;

    private final CompanyRepository companyRepository;

    private final TariffInfoService tariffInfoService;

    @Transactional
    public User changePassword(User user, ChangePasswordRequest request) {
        if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
            throw new NotValidationPasswordException("Current password is incorrect");
        }

        user.setPassword(passwordEncoder.encode(request.getNewPassword()));

        return userRepository.save(user);
    }

    public String userEmailById(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));

        return user.getEmail();
    }

    @Transactional
    public UserDTO updateUser(UserRequest userRequest, User currentUser) {
        User user = userRepository.findById(currentUser.getId())
                .orElseThrow(() -> new UserNotFoundException("User not found with id: " + currentUser.getId()));

        updateUserFromRequest(user, userRequest);

        User updatedUser = userRepository.save(user);

        return mapToDTO(updatedUser);
    }

    public UserDTO getUserInfo(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));

        return mapToDTO(user);
    }

    @Transactional
    public List<String> getUsersCompanyEmails(String company) {
        return userRepository.emailsByCompany(company);
    }

    @Transactional
    public User activateAndVerifyUser(long userId) {
        Optional<User> users = userRepository.findById(userId);
        User user = users.orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));
        user.setActive(true);
        user.setEmailVerified(true);

        return userRepository.save(user);
    }

    @Transactional
    public User createUser(RegRequest registerRequest, boolean verified, Long createrId) {
        User user = new User();
        user.setFullName(registerRequest.getFullName());
        user.setEmail(registerRequest.getEmail());
        user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
        user.setActive(!verified);
        user.setEmailVerified(!verified);

        AccountType accountType = AccountType.valueOf(registerRequest.getAccountType().toUpperCase());

        if (accountType == AccountType.B2B && registerRequest.getCompanyName() != null) {
            try {
                createCompanyAccount(registerRequest, user);
            } catch (CompanyAlreadyExist exist) {
                throw new CompanyAlreadyExist("Company already exist");
            }
        } else if (accountType == AccountType.B2C && registerRequest.getCompanyName() == null) {
            createIndividualAccount(user);
        } else {
            createIndividualCompanyAccount(user, createrId);
        }

        if (registerRequest.getPhone() != null) {
            user.setPhone(registerRequest.getPhone());
        }

        return userRepository.save(user);
    }

    public Optional<User> getUserById(Long userId) {
        return userRepository.findById(userId);
    }

    public boolean existsByEmailOrPhone(String email, String phone){
        return userRepository.existsByEmail(email) || userRepository.existsByPhone(phone);
    }

    private void createCompanyAccount(RegRequest request, User user) {
        Optional<Company> byInn = companyRepository.findByInn(request.getInn());

        if (byInn.isPresent()) {
            throw new CompanyAlreadyExist("Company was already created");
        }

        Company company = new Company();
        company.setCompanyName(request.getCompanyName());
        company.setInn(request.getInn());

        TariffInfo companyTariff = tariffInfoService.createTariffInfo(TariffType.DEMO);
        company.setTariffInfo(companyTariff);

        companyRepository.save(company);
        tariffInfoService.addTariffInfo(companyTariff);

        user.setCompany(company);
        user.grantPermission(Permission.COMPANY_ADMIN_PERMISSIONS);
    }

    private void createIndividualAccount(User user) {
        TariffInfo personalTariff = tariffInfoService.createTariffInfo(TariffType.DEMO);

        tariffInfoService.addTariffInfo(personalTariff);

        user.setPersonalTariffInfo(personalTariff);
        user.grantPermission(Permission.DEFAULT_USER_PERMISSIONS);
    }


    private void createIndividualCompanyAccount(User user, Long companyUserId) {
        User companyUser = getUserById(companyUserId).orElseThrow(() ->
                new EntityNotFoundException("Not found user id: " + companyUserId));

        Company company = companyUser.getCompany();
        if (company == null) {
            throw new IllegalStateException("Not have company");
        }

        TariffInfo tariffInfo = company.getTariffInfo();
        if (tariffInfo == null) {
            throw new IllegalStateException("Company not have tariff");
        }

        user.setCompany(company);
        user.grantPermission(Permission.DEFAULT_USER_PERMISSIONS);
    }

    private void updateUserFromRequest(User user, UserRequest request) {
        if (request.getFullName() != null) {
            user.setFullName(request.getFullName());
        }

        if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
            if (userRepository.existsByEmail(request.getEmail())) {
                throw new InvalidUserEmail("Email already exists: " + request.getEmail());
            }
            user.setEmail(request.getEmail());
        }

        if (request.getPhone() != null) {
            user.setPhone(request.getPhone());
        }
        if (request.getGenderType() != null) {
            user.setGenderType(request.getGenderType());
        }
        if (request.getBirthday() != null) {
            user.setBirthday(request.getBirthday());
        }
    }

    private UserDTO mapToDTO(User user) {
        TariffInfo personalTariffInfo = user.getPersonalTariffInfo();
        Tariff tariff = personalTariffInfo.getTariff();
        Long fileOnDisk = user.canManageCompanySettings() ?
                fileStatsService.calculateCompanyFileSize(user.getId()):
                fileStatsService.calculateUserFileSize(user.getId());

        Integer filesCount =  user.canManageCompanySettings() ?
                fileStatsService.filesCompanyCount(user.getId()):
                fileStatsService.filesUserCount(user.getId());

        TariffInfoDTO infoDTO = TariffInfoDTO.builder().tokens(personalTariffInfo.getTokens())
                .id(personalTariffInfo.getId())
                .tariffName(tariff.getName())
                .status(personalTariffInfo.getStatus().name())
                .tariffId(tariff.getId())
                .currentFileCounts(filesCount)
                .maxFileCounts(tariff.getMaxFilesCount())
                .currentFileOnDisk(fileOnDisk)
                .maxFileOnDisk(tariff.getDiskSize())
                .startTariff(personalTariffInfo.getStartTariff())
                .endTariff(personalTariffInfo.getEndTariff())
                .tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
                .build();

        return UserDTO.builder()
                .fullName(user.getFullName())
                .company(user.getCompanyName())
                .email(user.getEmail())
                .active(user.isActive())
                .phone(user.getPhone())
                .genderType(user.getGenderType())
                .birthday(user.getBirthday())
                .createdAt(user.getCreatedAt())
                .tariffInfo(infoDTO)
                .verifiedStatus(user.getVerificationStatus())
                .build();
    }
}
