package com.imprimelibros.erp.users;

import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

import java.util.Set;

import org.hibernate.annotations.Formula;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.hibernate.annotations.SQLRestriction;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(name = "uk_users_username", columnNames = "username")
})
@SQLRestriction("deleted = false")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "fullname")
    @NotBlank(message = "{validation.required}")
    private String fullName;

    @Column(name = "username", nullable = false, length = 190)
    @Email(message = "{validation.email}")
    @NotBlank(message = "{validation.required}")
    private String userName;

    @Column(name = "password")
    @NotBlank(message = "{validation.required}")
    private String password;

    @Column(name = "enabled")
    private boolean enabled;

    @Column(name = "deleted", nullable = false)
    private boolean deleted = false;

    @Column(name = "deleted_at")
    private LocalDateTime deletedAt;

    @Column(name = "deleted_by")
    private Long deletedBy;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE,
            CascadeType.REMOVE }, orphanRemoval = true)
    @SQLRestriction("deleted = false")
    @JsonIgnore
    private Set<UserRole> rolesLink = new HashSet<>();

    // SUPERADMIN=3, ADMIN=2, USER=1 (ajusta a tus nombres reales)
    @Formula("""
            (
              select coalesce(max(
                case r.name
                  when 'SUPERADMIN' then 3
                  when 'ADMIN'      then 2
                  else 1
                end
              ), 0)
              from users_roles ur
              join roles r on r.id = ur.role_id
              where ur.user_id = id
            )
            """)
    private Integer roleRank;

    @Formula("""
            (select group_concat(lower(r.name) order by r.name separator ', ')
                from users_roles ur join roles r on r.id = ur.role_id
                where ur.user_id = id)
                """)
    private String rolesConcat;

    /* Constructors */
    public User() {
    }

    public User(String fullName, String userName, String password, boolean enabled) {
        this.fullName = fullName;
        this.userName = userName;
        this.password = password;
        this.enabled = enabled;
    }

    public User(String fullName, String userName, String password, boolean enabled,
            Set<UserRole> roles) {
        this.fullName = fullName;
        this.userName = userName;
        this.password = password;
        this.enabled = enabled;
        this.rolesLink = roles;
    }

    /* Getters and Setters */

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Transient
    public Set<Role> getRoles() {
        return rolesLink.stream()
                .filter(ur -> !ur.isDeleted())
                .map(UserRole::getRole)
                .collect(Collectors.toSet());
    }

    @JsonProperty("roles")
    public List<String> getRoleNames() {
        return this.getRoles().stream()
                .map(Role::getName)
                .filter(java.util.Objects::nonNull)
                .map(String::trim)
                .toList();
    }

    public void setRoles(Set<Role> desired) {
        if (desired == null)
            desired = Collections.emptySet();

        // 1) ids deseados
        Set<Long> desiredIds = desired.stream()
                .map(Role::getId)
                .collect(Collectors.toSet());

        // 2) Soft-delete de vínculos activos que ya no se desean
        this.rolesLink.stream()
                .filter(ur -> !ur.isDeleted() && !desiredIds.contains(ur.getRole().getId()))
                .forEach(UserRole::softDelete);

        // 3) Para cada rol deseado: si hay vínculo borrado => reactivar; si no existe
        // => crear
        for (Role role : desired) {
            // ya activo
            boolean activeExists = this.rolesLink.stream()
                    .anyMatch(ur -> !ur.isDeleted() && ur.getRole().getId().equals(role.getId()));
            if (activeExists)
                continue;

            // existe borrado => reactivar
            Optional<UserRole> deletedLink = this.rolesLink.stream()
                    .filter(ur -> ur.isDeleted() && ur.getRole().getId().equals(role.getId()))
                    .findFirst();

            if (deletedLink.isPresent()) {
                UserRole ur = deletedLink.get();
                ur.setDeleted(false);
                ur.setDeletedAt(null);
            } else {
                // crear nuevo vínculo
                UserRole ur = new UserRole(this, role);
                this.rolesLink.add(ur);
                // si tienes la colección inversa en Role:
                role.getUsersLink().add(ur);
            }
        }
    }

    public Integer getRoleRank() {
        return roleRank;
    }

    public String getRolesConcat() {
        return rolesConcat;
    }

    public boolean isDeleted() {
        return deleted;
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    public LocalDateTime getDeletedAt() {
        return deletedAt;
    }

    public void setDeletedAt(LocalDateTime deletedAt) {
        this.deletedAt = deletedAt;
    }

    public Long getDeletedBy() {
        return deletedBy;
    }

    public void setDeletedBy(Long deletedBy) {
        this.deletedBy = deletedBy;
    }

    public Set<UserRole> getRolesLink() {
        return rolesLink;
    }

    public void setRolesLink(Set<UserRole> rolesLink) {
        this.rolesLink = rolesLink;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", fullName='" + fullName + '\'' +
                ", userName='" + userName + '\'' +
                ", enabled=" + enabled +
                ", roles=" + getRoles() +
                '}';
    }

}
