package de.effigenix.domain.usermanagement;

import de.effigenix.shared.common.Result;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * User Entity (Simple Entity, NOT an Aggregate).
 *
 * Generic Subdomain → Minimal DDD:
 * - Immutable: All business methods return new instances via Result
 * - Validation via Result type in factory method
 * - Status transition guards enforce valid state machine
 * - NO domain events
 *
 * Invariant: username is non-blank, email is valid, passwordHash is non-null, status is non-null
 * Uniqueness (username, email) is enforced in the Application Layer (repository concern)
 *
 * Status transitions:
 * - ACTIVE → LOCKED (lock)
 * - LOCKED → ACTIVE (unlock)
 * - ACTIVE | LOCKED → INACTIVE (deactivate)
 * - INACTIVE → ACTIVE (activate)
 */
public class User {
    private final UserId id;
    private final String username;
    private final String email;
    private final PasswordHash passwordHash;
    private final Set<Role> roles;
    private final String branchId;
    private final UserStatus status;
    private final OffsetDateTime createdAt;
    private final OffsetDateTime lastLogin;

    private User(
            UserId id,
            String username,
            String email,
            PasswordHash passwordHash,
            Set<Role> roles,
            String branchId,
            UserStatus status,
            OffsetDateTime createdAt,
            OffsetDateTime lastLogin
    ) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.passwordHash = passwordHash;
        this.roles = roles != null ? Set.copyOf(roles) : Set.of();
        this.branchId = branchId;
        this.status = status;
        this.createdAt = createdAt != null ? createdAt : OffsetDateTime.now(ZoneOffset.UTC);
        this.lastLogin = lastLogin;
    }

    /**
     * Factory method for creating a new active user with validation.
     */
    public static Result<UserError, User> create(
            String username,
            String email,
            PasswordHash passwordHash,
            Set<Role> roles,
            String branchId
    ) {
        if (username == null || username.isBlank()) {
            return Result.failure(new UserError.InvalidUsername("Username cannot be null or empty"));
        }
        if (email == null || email.isBlank() || !isValidEmail(email)) {
            return Result.failure(new UserError.InvalidEmail(email != null ? email : "null"));
        }
        if (passwordHash == null) {
            return Result.failure(new UserError.NullPasswordHash());
        }

        return Result.success(new User(
                UserId.generate(),
                username,
                email,
                passwordHash,
                roles,
                branchId,
                UserStatus.ACTIVE,
                OffsetDateTime.now(ZoneOffset.UTC),
                null
        ));
    }

    /**
     * Reconstitute from persistence - no validation (data already validated on creation).
     */
    public static User reconstitute(
            UserId id,
            String username,
            String email,
            PasswordHash passwordHash,
            Set<Role> roles,
            String branchId,
            UserStatus status,
            OffsetDateTime createdAt,
            OffsetDateTime lastLogin
    ) {
        return new User(id, username, email, passwordHash, roles, branchId, status, createdAt, lastLogin);
    }

    // ==================== Business Methods (Wither-Pattern) ====================

    public Result<UserError, User> withLastLogin(OffsetDateTime timestamp) {
        return Result.success(new User(id, username, email, passwordHash, roles, branchId, status, createdAt, timestamp));
    }

    public Result<UserError, User> changePassword(PasswordHash newPasswordHash) {
        if (newPasswordHash == null) {
            return Result.failure(new UserError.NullPasswordHash());
        }
        return Result.success(new User(id, username, email, newPasswordHash, roles, branchId, status, createdAt, lastLogin));
    }

    public Result<UserError, User> lock() {
        if (status != UserStatus.ACTIVE) {
            return Result.failure(new UserError.InvalidStatusTransition(status, UserStatus.LOCKED));
        }
        return Result.success(new User(id, username, email, passwordHash, roles, branchId, UserStatus.LOCKED, createdAt, lastLogin));
    }

    public Result<UserError, User> unlock() {
        if (status != UserStatus.LOCKED) {
            return Result.failure(new UserError.InvalidStatusTransition(status, UserStatus.ACTIVE));
        }
        return Result.success(new User(id, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, createdAt, lastLogin));
    }

    public Result<UserError, User> deactivate() {
        if (status != UserStatus.ACTIVE && status != UserStatus.LOCKED) {
            return Result.failure(new UserError.InvalidStatusTransition(status, UserStatus.INACTIVE));
        }
        return Result.success(new User(id, username, email, passwordHash, roles, branchId, UserStatus.INACTIVE, createdAt, lastLogin));
    }

    public Result<UserError, User> activate() {
        if (status != UserStatus.INACTIVE) {
            return Result.failure(new UserError.InvalidStatusTransition(status, UserStatus.ACTIVE));
        }
        return Result.success(new User(id, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, createdAt, lastLogin));
    }

    public Result<UserError, User> assignRole(Role role) {
        if (role == null) {
            return Result.failure(new UserError.NullRole());
        }
        Set<Role> newRoles = new HashSet<>(roles);
        newRoles.add(role);
        return Result.success(new User(id, username, email, passwordHash, newRoles, branchId, status, createdAt, lastLogin));
    }

    public Result<UserError, User> removeRole(Role role) {
        if (role == null) {
            return Result.failure(new UserError.NullRole());
        }
        Set<Role> newRoles = new HashSet<>(roles);
        newRoles.remove(role);
        return Result.success(new User(id, username, email, passwordHash, newRoles, branchId, status, createdAt, lastLogin));
    }

    public Result<UserError, User> updateEmail(String newEmail) {
        if (newEmail == null || newEmail.isBlank()) {
            return Result.failure(new UserError.InvalidEmail("null or empty"));
        }
        if (!isValidEmail(newEmail)) {
            return Result.failure(new UserError.InvalidEmail(newEmail));
        }
        return Result.success(new User(id, username, newEmail, passwordHash, roles, branchId, status, createdAt, lastLogin));
    }

    public Result<UserError, User> updateBranch(String newBranchId) {
        return Result.success(new User(id, username, email, passwordHash, roles, newBranchId, status, createdAt, lastLogin));
    }

    // ==================== Query Methods ====================

    public boolean isActive() {
        return status == UserStatus.ACTIVE;
    }

    public boolean isLocked() {
        return status == UserStatus.LOCKED;
    }

    public Set<Permission> getAllPermissions() {
        Set<Permission> allPermissions = new HashSet<>();
        for (Role role : roles) {
            allPermissions.addAll(role.permissions());
        }
        return Collections.unmodifiableSet(allPermissions);
    }

    public boolean hasPermission(Permission permission) {
        return getAllPermissions().contains(permission);
    }

    // ==================== Validation Helpers ====================

    private static boolean isValidEmail(String email) {
        return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
    }

    // ==================== Getters ====================

    public UserId id() { return id; }
    public String username() { return username; }
    public String email() { return email; }
    public PasswordHash passwordHash() { return passwordHash; }
    public Set<Role> roles() { return Collections.unmodifiableSet(roles); }
    public String branchId() { return branchId; }
    public UserStatus status() { return status; }
    public OffsetDateTime createdAt() { return createdAt; }
    public OffsetDateTime lastLogin() { return lastLogin; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof User other)) return false;
        return id.equals(other.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", username='" + username + "', email='" + email + "', status=" + status + "}";
    }
}
