package org.raddatz.familienarchiv.user;

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

import org.hibernate.annotations.CreationTimestamp;
import org.raddatz.familienarchiv.user.CreateUserRequest;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import jakarta.persistence.PostLoad;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;

@Entity
@Table(name = "app_users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppUser {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private UUID id;

    @Column(unique = true, nullable = false)
    @NotBlank
    @Email
    @Pattern(regexp = "^[^:]+$", message = "Email must not contain a colon")
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private String email;

    @Column(nullable = false)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;

    private String firstName;
    private String lastName;
    private LocalDate birthDate;

    @Column(columnDefinition = "TEXT")
    private String contact;

    @Builder.Default
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private boolean enabled = true;

    @Column(nullable = false)
    @Builder.Default
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private boolean notifyOnReply = false;

    @Column(nullable = false)
    @Builder.Default
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private boolean notifyOnMention = false;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "app_users_groups", joinColumns = @JoinColumn(name = "app_user_id"), inverseJoinColumns = @JoinColumn(name = "group_id"))
    @Builder.Default
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private Set<UserGroup> groups = new HashSet<>();

    @CreationTimestamp
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createdAt;

    @Column(nullable = false)
    @Builder.Default
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private String color = "";

    private static final String[] PALETTE = {
        "#7a4f9a", "#5a8a6a", "#3060b0", "#a0522d", "#c0446e", "#c17a00", "#0e7490", "#1d4ed8"
    };

    public static String computeColor(UUID id) {
        // Math.floorMod avoids the Integer.MIN_VALUE overflow trap in Math.abs(hashCode())
        return PALETTE[Math.floorMod(id.hashCode(), PALETTE.length)];
    }

    @PrePersist
    @PreUpdate
    @PostLoad
    void deriveColor() {
        if (id != null && (color == null || color.isEmpty())) {
            this.color = computeColor(id);
        }
    }

    public boolean hasPermission(String permission) {
        if (groups == null || groups.isEmpty()) {
            return false;
        }
        return this.groups.stream().anyMatch(group -> group.getPermissions().contains(permission));
    }

    public AppUser updateFromRequest(CreateUserRequest request, PasswordEncoder passwordEncoder, Set<UserGroup> groups) {
        if (request.getEmail() != null && !request.getEmail().isBlank()) {
            this.email = request.getEmail();
        }

        if (request.getInitialPassword() != null && !request.getInitialPassword().isBlank()) {
            this.password = passwordEncoder.encode(request.getInitialPassword());
        }

        if (groups != null && !groups.isEmpty()) {
            this.groups = groups;
        }

        return this;
    }
}
