<?php

declare(strict_types=1);

namespace App\Models;

use App\Core\Model;
use PDO;
use RuntimeException;

final class Utilisateur extends Model
{
    /** @var array<string,bool>|null */
    private ?array $columnMap = null;

    /**
     * @return array<int,array<string,mixed>>
     */
    public function all(): array
    {
        $lastLogin = $this->hasColumn('derniere_connexion')
            ? 'derniere_connexion'
            : 'NULL AS derniere_connexion';

        $stmt = $this->db->query("
            SELECT
                id,
                nom,
                prenom,
                login,
                email,
                role,
                actif,
                {$lastLogin},
                date_creation,
                date_modification
            FROM utilisateurs
            ORDER BY actif DESC, nom ASC, prenom ASC
        ");

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * @return array<string,mixed>|null
     */
    public function find(int $id): ?array
    {
        $passwordColumn = $this->passwordColumn();
        $lastLogin = $this->hasColumn('derniere_connexion')
            ? 'derniere_connexion'
            : 'NULL AS derniere_connexion';

        $stmt = $this->db->prepare("
            SELECT
                id,
                nom,
                prenom,
                login,
                email,
                {$passwordColumn} AS password_hash,
                role,
                actif,
                {$lastLogin},
                date_creation,
                date_modification
            FROM utilisateurs
            WHERE id = :id
            LIMIT 1
        ");

        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ?: null;
    }

    /**
     * @return array<string,mixed>|null
     */
    public function findByLogin(string $login): ?array
    {
        $passwordColumn = $this->passwordColumn();
        $lastLogin = $this->hasColumn('derniere_connexion')
            ? 'derniere_connexion'
            : 'NULL AS derniere_connexion';

        $stmt = $this->db->prepare("
            SELECT
                id,
                nom,
                prenom,
                login,
                email,
                {$passwordColumn} AS password_hash,
                role,
                actif,
                {$lastLogin}
            FROM utilisateurs
            WHERE login = :login
            LIMIT 1
        ");

        $stmt->execute(['login' => trim($login)]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ?: null;
    }

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

        $stmt = $this->db->prepare("
            INSERT INTO utilisateurs (
                nom,
                prenom,
                login,
                email,
                {$passwordColumn},
                role,
                actif
            ) VALUES (
                :nom,
                :prenom,
                :login,
                :email,
                :password_hash,
                :role,
                :actif
            )
        ");

        $stmt->execute([
            'nom' => trim((string)($data['nom'] ?? '')),
            'prenom' => trim((string)($data['prenom'] ?? '')),
            'login' => trim((string)($data['login'] ?? '')),
            'email' => $this->nullableText($data['email'] ?? null),
            'password_hash' => (string)($data['password_hash'] ?? ''),
            'role' => strtoupper(trim((string)($data['role'] ?? 'LECTURE'))),
            'actif' => !empty($data['actif']) ? 1 : 0,
        ]);

        return (int)$this->db->lastInsertId();
    }

    public function update(int $id, array $data): void
    {
        $stmt = $this->db->prepare("
            UPDATE utilisateurs
            SET
                nom = :nom,
                prenom = :prenom,
                login = :login,
                email = :email,
                role = :role,
                actif = :actif
            WHERE id = :id
        ");

        $stmt->execute([
            'id' => $id,
            'nom' => trim((string)($data['nom'] ?? '')),
            'prenom' => trim((string)($data['prenom'] ?? '')),
            'login' => trim((string)($data['login'] ?? '')),
            'email' => $this->nullableText($data['email'] ?? null),
            'role' => strtoupper(trim((string)($data['role'] ?? 'LECTURE'))),
            'actif' => !empty($data['actif']) ? 1 : 0,
        ]);
    }

    public function updatePassword(int $id, string $passwordHash): void
    {
        $passwordColumn = $this->passwordColumn();

        $stmt = $this->db->prepare("
            UPDATE utilisateurs
            SET {$passwordColumn} = :password_hash
            WHERE id = :id
        ");

        $stmt->execute([
            'id' => $id,
            'password_hash' => $passwordHash,
        ]);
    }

    public function updateLastLogin(int $id): void
    {
        if (!$this->hasColumn('derniere_connexion')) {
            return;
        }

        $stmt = $this->db->prepare("
            UPDATE utilisateurs
            SET derniere_connexion = NOW()
            WHERE id = :id
        ");

        $stmt->execute(['id' => $id]);
    }

    public function deactivate(int $id): void
    {
        $stmt = $this->db->prepare("
            UPDATE utilisateurs
            SET actif = 0
            WHERE id = :id
        ");

        $stmt->execute(['id' => $id]);
    }

    public function loginExists(string $login, ?int $exceptId = null): bool
    {
        $sql = "SELECT COUNT(*) FROM utilisateurs WHERE login = :login";
        $params = ['login' => trim($login)];

        if ($exceptId !== null) {
            $sql .= ' AND id <> :except_id';
            $params['except_id'] = $exceptId;
        }

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return (int)$stmt->fetchColumn() > 0;
    }

    public function emailExists(string $email, ?int $exceptId = null): bool
    {
        $email = trim($email);

        if ($email === '') {
            return false;
        }

        $sql = "SELECT COUNT(*) FROM utilisateurs WHERE email = :email";
        $params = ['email' => $email];

        if ($exceptId !== null) {
            $sql .= ' AND id <> :except_id';
            $params['except_id'] = $exceptId;
        }

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return (int)$stmt->fetchColumn() > 0;
    }

    private function passwordColumn(): string
    {
        foreach (['mot_de_passe', 'password_hash', 'password'] as $column) {
            if ($this->hasColumn($column)) {
                return $column;
            }
        }

        throw new RuntimeException(
            'Aucune colonne de mot de passe compatible n’a été trouvée dans la table utilisateurs.'
        );
    }

    private function hasColumn(string $column): bool
    {
        if ($this->columnMap === null) {
            $this->columnMap = [];
            $stmt = $this->db->query('SHOW COLUMNS FROM utilisateurs');

            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                $name = (string)($row['Field'] ?? '');
                if ($name !== '') {
                    $this->columnMap[$name] = true;
                }
            }
        }

        return isset($this->columnMap[$column]);
    }

    private function nullableText(mixed $value): ?string
    {
        $value = trim((string)$value);

        return $value !== '' ? $value : null;
    }
}
