package es.tatvil.taiageweb.servicio;

import es.tatvil.taiageweb.modelo.Usuario;
import es.tatvil.taiageweb.repositorio.UsuarioRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Servicio de negocio para la gestión de usuarios.
 *
 * <p>Implementa {@link UserDetailsService} para la integración con Spring Security
 * y centraliza todas las operaciones sobre la entidad {@link Usuario}: registro,
 * activación, asignación de roles y eliminación.</p>
 */
@Service
public class UsuarioService implements UserDetailsService {

    private final UsuarioRepository repo;
    private final PasswordEncoder encoder;

    /**
     * Constructor con inyección de dependencias.
     *
     * @param repo    repositorio de usuarios
     * @param encoder encoder de contraseñas BCrypt
     */
    public UsuarioService(UsuarioRepository repo, PasswordEncoder encoder) {
        this.repo = repo;
        this.encoder = encoder;
    }

    // ── Spring Security ──────────────────────────────────

    /**
     * Carga los detalles de un usuario por su email para Spring Security.
     *
     * @param email email del usuario (campo {@code username} del formulario de login)
     * @return detalles del usuario listos para la autenticación
     * @throws UsernameNotFoundException si no existe ningún usuario con ese email
     */
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Usuario u = repo.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado: " + email));

        var authorities = u.getRoles().stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return new org.springframework.security.core.userdetails.User(
                u.getEmail(), u.getPassword(),
                u.isHabilitado(),   // enabled
                true,               // accountNonExpired
                true,               // credentialsNonExpired
                true,               // accountNonLocked
                authorities
        );
    }

    // ── Auto-registro (pendiente de activación) ──────────────
    /**
     * Registra un nuevo usuario con la cuenta deshabilitada hasta que el admin la active.
     *
     * @param email        email del nuevo usuario
     * @param passwordPlana contraseña en texto plano (mínimo 8 caracteres)
     * @throws IllegalArgumentException si el email ya está registrado o la contraseña es demasiado corta
     */    @Transactional
    public void registrar(String email, String passwordPlana) {
        if (repo.existsByEmail(email)) {
            throw new IllegalArgumentException("El email ya está registrado");
        }
        if (passwordPlana.length() < 8) {
            throw new IllegalArgumentException("La contraseña debe tener al menos 8 caracteres");
        }
        Usuario u = new Usuario();
        u.setEmail(email);
        u.setPassword(encoder.encode(passwordPlana));
        u.setHabilitado(false);           // pendiente de activación por admin
        u.setRoles(new HashSet<>(Set.of("ROLE_USER")));
        repo.save(u);
    }

    // ── Consultas ────────────────────────────────────────────
    /**
     * Devuelve todos los usuarios registrados.
     *
     * @return lista de usuarios ordenada por ID
     */    public List<Usuario> listarTodos() {
        return repo.findAll();
    }

    // ── Acciones de admin ────────────────────────────────────
    /**
     * Invierte el estado de habilitación de la cuenta de un usuario.
     *
     * @param id identificador del usuario
     * @throws java.util.NoSuchElementException si no existe un usuario con ese id
     */    @Transactional
    public void toggleHabilitado(Long id) {
        Usuario u = repo.findById(id).orElseThrow();
        u.setHabilitado(!u.isHabilitado());
    }

    /**
     * Concede o revoca el rol {@code ROLE_PAGADO} de un usuario.
     *
     * @param id identificador del usuario
     * @throws java.util.NoSuchElementException si no existe un usuario con ese id
     */
    @Transactional
    public void toggleRolPagado(Long id) {
        Usuario u = repo.findById(id).orElseThrow();
        if (u.getRoles().contains("ROLE_PAGADO")) {
            u.getRoles().remove("ROLE_PAGADO");
        } else {
            u.getRoles().add("ROLE_PAGADO");
        }
    }

    /**
     * Concede acceso al curso tras un pago confirmado por Stripe.
     *
     * <p>Añade {@code ROLE_PAGADO} y activa la cuenta si estuviera deshabilitada.
     * Este método <em>nunca</em> quita el rol una vez concedido.</p>
     *
     * @param id identificador del usuario
     * @throws java.util.NoSuchElementException si no existe un usuario con ese id
     */
    @Transactional
    public void darAccesoPagado(Long id) {
        Usuario u = repo.findById(id).orElseThrow();
        u.getRoles().add("ROLE_PAGADO");
        u.setHabilitado(true);
    }

    /**
     * Crea un usuario directamente activo desde el panel de administración.
     *
     * @param email        email del nuevo usuario
     * @param passwordPlana contraseña en texto plano
     * @param pagado       si {@code true}, se añade {@code ROLE_PAGADO} además de {@code ROLE_USER}
     * @throws IllegalArgumentException si el email ya está registrado
     */
    @Transactional
    public void crearPorAdmin(String email, String passwordPlana, boolean pagado) {
        if (repo.existsByEmail(email)) {
            throw new IllegalArgumentException("El email ya está registrado");
        }
        Usuario u = new Usuario();
        u.setEmail(email);
        u.setPassword(encoder.encode(passwordPlana));
        u.setHabilitado(true);
        Set<String> roles = new HashSet<>();
        roles.add("ROLE_USER");
        if (pagado) roles.add("ROLE_PAGADO");
        u.setRoles(roles);
        repo.save(u);
    }

    /**
     * Elimina un usuario de la base de datos.
     *
     * @param id identificador del usuario a eliminar
     */
    @Transactional
    public void eliminar(Long id) {
        repo.deleteById(id);
    }
}
