<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Utilisateur;
use InvalidArgumentException;
use RuntimeException;
use App\Core\Auth;

final class UtilisateurService extends BaseService
{
    private const ROLES = [
        'ADMINISTRATEUR',
        'COMMERCIAL',
        'MAGASIN',
        'LECTURE',
    ];

    public function __construct(
        private readonly Utilisateur $users = new Utilisateur()
    ) {
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public function all(): array
    {
        return $this->users->all();
    }

    /**
     * @return array<string,mixed>
     */
    public function find(int $id): array
    {
        if ($id <= 0) {
            throw new InvalidArgumentException(
                'Utilisateur invalide.'
            );
        }

        $user = $this->users->find($id);

        if ($user === null) {
            throw new RuntimeException(
                'Utilisateur introuvable.'
            );
        }

        return $user;
    }

    public function create(array $data): int
    {
        $validated = $this->validate($data);

        $password = (string)($data['password'] ?? '');
        $passwordConfirmation =
            (string)($data['password_confirmation'] ?? '');

        $this->validatePassword(
            $password,
            $passwordConfirmation,
            true
        );

        if ($this->users->loginExists($validated['login'])) {
            throw new RuntimeException(
                'Cet identifiant de connexion est déjà utilisé.'
            );
        }

        if (
            $validated['email'] !== ''
            && $this->users->emailExists($validated['email'])
        ) {
            throw new RuntimeException(
                'Cette adresse e-mail est déjà utilisée.'
            );
        }

        $validated['password_hash'] = password_hash(
            $password,
            PASSWORD_DEFAULT
        );

        return $this->users->create($validated);
    }

    public function update(int $id, array $data): void
    {
        $this->find($id);

        $validated = $this->validate($data);

        if ($this->users->loginExists($validated['login'], $id)) {
            throw new RuntimeException(
                'Cet identifiant de connexion est déjà utilisé.'
            );
        }

        if (
            $validated['email'] !== ''
            && $this->users->emailExists($validated['email'], $id)
        ) {
            throw new RuntimeException(
                'Cette adresse e-mail est déjà utilisée.'
            );
        }

        $this->users->update($id, $validated);
    }

    public function changePassword(
        int $id,
        string $password,
        string $passwordConfirmation
    ): void {
        $this->find($id);

        $this->validatePassword(
            $password,
            $passwordConfirmation,
            true
        );

        $passwordHash = password_hash(
            $password,
            PASSWORD_DEFAULT
        );

        $this->users->updatePassword(
            $id,
            $passwordHash
        );
    }

    public function deactivate(int $id): void
    {
        $user = $this->find($id);

        if (
            strtoupper((string)($user['role'] ?? ''))
            === 'ADMINISTRATEUR'
            && $this->countActiveAdministrators() <= 1
        ) {
            throw new RuntimeException(
                'Impossible de désactiver le dernier administrateur actif.'
            );
        }

        $this->users->deactivate($id);
    }
/**
 * Authentifie un utilisateur et ouvre sa session.
 *
 * @return array<string,mixed>
 */
public function authenticate(
    string $login,
    string $password
): array {
    $login = trim($login);

    if ($login === '') {
        throw new InvalidArgumentException(
            'L’identifiant de connexion est obligatoire.'
        );
    }

    if ($password === '') {
        throw new InvalidArgumentException(
            'Le mot de passe est obligatoire.'
        );
    }

    $user = $this->users->findByLogin($login);

    /*
     * Le même message est volontairement utilisé lorsque
     * l’utilisateur ou le mot de passe est incorrect.
     * Cela évite de révéler l’existence d’un compte.
     */
    if (
        $user === null
        || !password_verify(
            $password,
            (string)($user['password_hash'] ?? '')
        )
    ) {
        throw new RuntimeException(
            'Identifiant ou mot de passe incorrect.'
        );
    }

    if ((int)($user['actif'] ?? 0) !== 1) {
        throw new RuntimeException(
            'Ce compte utilisateur est désactivé.'
        );
    }

    /*
     * Actualise automatiquement le hachage lorsque PHP
     * recommande un algorithme ou des paramètres plus récents.
     */
    if (
        password_needs_rehash(
            (string)$user['password_hash'],
            PASSWORD_DEFAULT
        )
    ) {
        $this->users->updatePassword(
            (int)$user['id'],
            password_hash(
                $password,
                PASSWORD_DEFAULT
            )
        );
    }

    $this->users->updateLastLogin(
        (int)$user['id']
    );

    Auth::login($user);

    /*
     * Le hachage ne doit jamais être retourné au contrôleur
     * ni conservé ailleurs que dans la base de données.
     */
    unset($user['password_hash']);

    return $user;
}

public function logout(): void
{
    Auth::logout();
}

    /**
     * @return array<int,string>
     */
    public function roles(): array
    {
        return self::ROLES;
    }

    /**
     * @return array<string,mixed>
     */
    private function validate(array $data): array
    {
        $nom = trim((string)($data['nom'] ?? ''));
        $prenom = trim((string)($data['prenom'] ?? ''));
        $login = trim((string)($data['login'] ?? ''));
        $email = trim((string)($data['email'] ?? ''));
        $role = strtoupper(
            trim((string)($data['role'] ?? 'LECTURE'))
        );

        if ($nom === '') {
            throw new InvalidArgumentException(
                'Le nom est obligatoire.'
            );
        }

        if ($login === '') {
            throw new InvalidArgumentException(
                'L’identifiant de connexion est obligatoire.'
            );
        }

        if (!preg_match('/^[A-Za-z0-9._-]{3,100}$/', $login)) {
            throw new InvalidArgumentException(
                'L’identifiant doit contenir au moins 3 caractères et uniquement des lettres, chiffres, points, tirets ou underscores.'
            );
        }

        if (
            $email !== ''
            && filter_var($email, FILTER_VALIDATE_EMAIL) === false
        ) {
            throw new InvalidArgumentException(
                'L’adresse e-mail n’est pas valide.'
            );
        }

        if (!in_array($role, self::ROLES, true)) {
            throw new InvalidArgumentException(
                'Le rôle sélectionné est invalide.'
            );
        }

        return [
            'nom' => $nom,
            'prenom' => $prenom,
            'login' => $login,
            'email' => $email,
            'role' => $role,
            'actif' => !empty($data['actif']) ? 1 : 0,
        ];
    }

    private function validatePassword(
        string $password,
        string $passwordConfirmation,
        bool $required
    ): void {
        if (!$required && $password === '') {
            return;
        }

        if (strlen($password) < 10) {
            throw new InvalidArgumentException(
                'Le mot de passe doit contenir au moins 10 caractères.'
            );
        }

        if (!preg_match('/[A-Z]/', $password)) {
            throw new InvalidArgumentException(
                'Le mot de passe doit contenir au moins une majuscule.'
            );
        }

        if (!preg_match('/[a-z]/', $password)) {
            throw new InvalidArgumentException(
                'Le mot de passe doit contenir au moins une minuscule.'
            );
        }

        if (!preg_match('/[0-9]/', $password)) {
            throw new InvalidArgumentException(
                'Le mot de passe doit contenir au moins un chiffre.'
            );
        }

        if (!preg_match('/[^A-Za-z0-9]/', $password)) {
            throw new InvalidArgumentException(
                'Le mot de passe doit contenir au moins un caractère spécial.'
            );
        }

        if ($password !== $passwordConfirmation) {
            throw new InvalidArgumentException(
                'La confirmation du mot de passe ne correspond pas.'
            );
        }
    }

    private function countActiveAdministrators(): int
    {
        $count = 0;

        foreach ($this->users->all() as $user) {
            if (
                (int)($user['actif'] ?? 0) === 1
                && strtoupper((string)($user['role'] ?? ''))
                    === 'ADMINISTRATEUR'
            ) {
                $count++;
            }
        }

        return $count;
    }
}