<?php

require_once BASE_PATH . '/config/database.php';

class PasswordReset
{
    private PDO $db;

    public function __construct()
    {
        $this->db = (new Database())->connect();
    }

    public function createForUser(int $userId): string
    {
        $token = bin2hex(random_bytes(32));
        $tokenHash = hash('sha256', $token);

        $this->db->beginTransaction();

        try {
            $invalidate = $this->db->prepare(
                'UPDATE password_resets
                 SET used_at = NOW()
                 WHERE user_id = :user_id AND used_at IS NULL'
            );
            $invalidate->execute(['user_id' => $userId]);

            $insert = $this->db->prepare(
                'INSERT INTO password_resets (
                    user_id, token_hash, expires_at
                 ) VALUES (
                    :user_id, :token_hash, DATE_ADD(NOW(), INTERVAL 60 MINUTE)
                 )'
            );
            $insert->execute([
                'user_id' => $userId,
                'token_hash' => $tokenHash
            ]);

            $this->db->commit();

            return $token;
        } catch (Throwable $e) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            throw $e;
        }
    }

    public function isValidToken(string $token): bool
    {
        return $this->findValidByToken($token) !== null;
    }

    public function resetPassword(string $token, string $passwordHash): ?int
    {
        $tokenHash = hash('sha256', $token);
        $this->db->beginTransaction();

        try {
            $stmt = $this->db->prepare(
                'SELECT id, user_id FROM password_resets
                 WHERE token_hash = :token_hash
                   AND used_at IS NULL
                   AND expires_at > NOW()
                 ORDER BY id DESC
                 LIMIT 1
                 FOR UPDATE'
            );
            $stmt->execute(['token_hash' => $tokenHash]);
            $reset = $stmt->fetch();

            if (!$reset) {
                $this->db->rollBack();
                return null;
            }

            $updateUser = $this->db->prepare(
                'UPDATE users SET password_hash = :password_hash
                 WHERE id = :user_id'
            );
            $updateUser->execute([
                'password_hash' => $passwordHash,
                'user_id' => (int) $reset['user_id']
            ]);

            $invalidate = $this->db->prepare(
                'UPDATE password_resets
                 SET used_at = NOW()
                 WHERE user_id = :user_id AND used_at IS NULL'
            );
            $invalidate->execute([
                'user_id' => (int) $reset['user_id']
            ]);

            $this->db->commit();

            return (int) $reset['user_id'];
        } catch (Throwable $e) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            throw $e;
        }
    }

    private function findValidByToken(string $token): ?array
    {
        if ($token === '' || !preg_match('/^[a-f0-9]{64}$/', $token)) {
            return null;
        }

        $stmt = $this->db->prepare(
            'SELECT id, user_id FROM password_resets
             WHERE token_hash = :token_hash
               AND used_at IS NULL
               AND expires_at > NOW()
             ORDER BY id DESC
             LIMIT 1'
        );
        $stmt->execute([
            'token_hash' => hash('sha256', $token)
        ]);

        $reset = $stmt->fetch();
        return $reset ?: null;
    }
}
