package dev.ksan.etfoglasiserver.service;

import dev.ksan.etfoglasiserver.dto.JwtResponseDTO;
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
import dev.ksan.etfoglasiserver.dto.UserDTO;
import dev.ksan.etfoglasiserver.dto.UserLoginDTO;
import dev.ksan.etfoglasiserver.model.NotificationMethod;
import dev.ksan.etfoglasiserver.model.Subject;
import dev.ksan.etfoglasiserver.model.User;
import dev.ksan.etfoglasiserver.repository.SubjectRepo;
import dev.ksan.etfoglasiserver.repository.UserRepo;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

@Service
@Transactional
public class UserService {
    @Autowired
    SubjectRepo subjectRepo;

  @Autowired UserRepo userRepo;

  @Autowired private AuthenticationManager authManager;

  @Autowired private JWTService jwtService;




  private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

  public List<UserDTO> getUsers() {
    return userRepo.findAll().stream().map(this::getUserDTO).collect(Collectors.toList());
  }

  public UserDTO getUserById(UUID userId) {
    User user = userRepo.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));

    return getUserDTO(user);
  }

  public void addUser(UserCreationDTO user) {

    if (userRepo.findByEmail(user.getEmail()).isPresent()) {
      throw new RuntimeException("User already exists");
    }
    if (this.isValidEmail(user.getEmail()) && this.isValidPassword(user.getPassword())) {
      userRepo.save(new User(user));
    }
  }

  public UserDTO updateUser(String email, UserCreationDTO user) {
    Optional<User> existingUserOpt = userRepo.findByEmail(email);

    if (userRepo.findByEmail(user.getNewEmail()).isPresent()) {
      throw new RuntimeException("Email taken");
    }
    if (existingUserOpt.isPresent()) {

      if (this.isValidEmail(user.getEmail())) {

        User existingUser = existingUserOpt.get();
        if (user.getPassword() != null && user.getPassword().length() > 0) {

          if (this.isValidPassword(user.getPassword())) {
            existingUser.setPassword(encoder.encode(user.getPassword()));
          } else throw new RuntimeException("Password too short");
        }
        if (user.getNewEmail() != null && !user.getNewEmail().equals(existingUser.getEmail())) {

          existingUser.setEmail(user.getNewEmail());
        } else {

          existingUser.setEmail(user.getEmail());
        }

        existingUser.setSubjectSet(user.getSubjectSet());

        userRepo.save(existingUser);

        return getUserDTO(existingUser);

      } else throw new RuntimeException("Invalid email");
    } else throw new RuntimeException("User not found");
  }

  public void deleteUser(String email) {
    User  user = userRepo.findByEmail(email).orElseThrow(() -> new RuntimeException("User not found"));

    user.getSubjectSet().clear();
    userRepo.save(user);
    userRepo.delete(user);
  }

  public UserDTO register(UserCreationDTO user) {

    if (userRepo.findByEmail(user.getEmail()).isPresent()) {
      throw new RuntimeException("User already exists");
    }
    if (this.isValidEmail(user.getEmail()) && this.isValidPassword(user.getPassword())) {
      User newUser = new User(user);
      newUser.setPassword(encoder.encode(user.getPassword()));
      userRepo.save(newUser);
      return getUserDTO(newUser);
    }
    throw new RuntimeException("Error creating user");
  }

  public UserDTO getUserDTO(User user) {

    return new UserDTO(
        user.getId(), user.getEmail(), user.getSubjectSet(), user.getNotificationMethod());
  }

  public boolean isValidPassword(String pass) {
    return pass.length() >= 8;
  }

  public List<User> findUsersBySubjectId(UUID id) {
    List<User> users = userRepo.findUsersBySubjectId(id);
    if (users.isEmpty()) return null;
    return users;
  }

  public boolean isValidEmail(String email) {
    if (email == null) {
      return false;
    }
    String regex = "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";

    if (email.length() < 256 && email.matches(regex)) {
      return true;
    }
    throw new RuntimeException("Invalid email");
  }

  public JwtResponseDTO verify(UserLoginDTO user) {

    authManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    user.getEmail(),
                    user.getPassword()
            )
    );

    User foundUser = userRepo.findByEmail(user.getEmail())
            .orElseThrow(() -> new RuntimeException("User not found"));

    String token = jwtService.generateToken(user.getEmail());

    return new JwtResponseDTO(
            token,
            foundUser.getEmail(),
            foundUser.getId().toString(),
            HttpStatus.OK.toString()
    );
  }

  public Optional<User> getAccountByEmail(String email) {
    return userRepo.findByEmail(email);
  }


    public UserDTO addSubject(String email, UUID subjectId) {

        User user = userRepo.findByEmail(email)
                .orElseThrow(() -> new RuntimeException("User not found"));

        Subject subject = subjectRepo.findById(subjectId)
                .orElseThrow(() -> new RuntimeException("Subject not found"));

        user.getSubjectSet().add(subject);

        userRepo.save(user);

        return getUserDTO(user);
    }

    public UserDTO removeSubject(String email, UUID subjectId) {

        User user = userRepo.findByEmail(email)
                .orElseThrow(() -> new RuntimeException("User not found"));

        Subject subject = subjectRepo.findById(subjectId)
                .orElseThrow(() -> new RuntimeException("Subject not found"));


        user.getSubjectSet().remove(subject);

        userRepo.save(user);

        return getUserDTO(user);
    }

  public void updateNotificationType(String email, NotificationMethod notificationType) {
    User user =  userRepo.findByEmail(email).orElseThrow(() -> new RuntimeException("User not found"));

    user.setNotificationMethod(notificationType);
    userRepo.save(user);
  }
}
