<?php

class Validator
{
    public static function name($name): bool
    {
        if (empty($name)) {
            throw new InvalidArgumentException('Name cannot be empty.');
        }

        self::printableAscii($name, 'Name');

        if (strlen($name) > MAX_NAME_LENGTH) {
            throw new InvalidArgumentException('Name is too long. Maximum allowed length is: ' . MAX_NAME_LENGTH);
        }

        return true;
    }

    public static function number($number, $label): bool
    {
        if (!is_numeric($number)) {
            throw new InvalidArgumentException("$label should be an number.");
        }

        return true;
    }

    public static function greaterThanOrEqual($number, $comparison, $label): bool
    {
        self::number($number, $label);

        if ($number < $comparison) {
            throw new InvalidArgumentException("$label should be greater than or equal to $comparison.");
        }
        return true;
    }

    public static function maxCMDBValueLength($value, $label): bool
    {

        $value = is_array($value) ? join("", $value) : $value;
        $value = is_string($value) ? $value : json_encode($value);
        if (strlen($value) > MAX_VALUE_LENGTH) {
            throw new InvalidArgumentException($label . ' is too long: ' . strlen($value) . ' bytes. Maximum allowed length is: ' . MAX_VALUE_LENGTH . ' bytes.');
        }

        return true;
    }

    public static function maxCMDBNameLength($name): bool
    {
        if (empty($name)) {
            throw new InvalidArgumentException('Name cannot be empty.');
        }
        self::printableAscii($name, 'Name');
        if (strlen($name) > MAX_CMDB_NAME_LENGTH) {
            throw new InvalidArgumentException('Name is too long: ' . strlen($name) . '. Maximum allowed length is: ' . MAX_CMDB_NAME_LENGTH);
        }
        return true;
    }

    public static function password($password, ?string $username = null): bool
    {
        $cfSettingsService = CfSettings::getInstance();
        $minPasswordLength = $cfSettingsService->getSetting(SettingsHelper::MIN_PASSWORD_LENGTH);
        if (mb_strlen($password) < $minPasswordLength) {
            throw new InvalidArgumentException("Password is too short. The minimum length is: $minPasswordLength.");
        }

        $zxcvbn = new \ZxcvbnPhp\Zxcvbn();
        $strong = $zxcvbn->passwordStrength($password);

        if ($cfSettingsService->getSetting(SettingsHelper::PASSWORD_COMPLEXITY) > $strong['score']) {
            $suggestion = $strong['feedback']['suggestions'][0] ?? '';
            throw new InvalidArgumentException("Password too weak. $suggestion");
        }

        if ($username !== null && CFAuthService::checkPasswordValid($username, $password)) {
            throw new InvalidArgumentException('New password should be different from previous password');
        }

        return true;
    }

    public static function email(string $email): bool
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format.');
        }

        return true;
    }

    public static function rolesExists(array $roles): bool
    {
        $existingRoles = (new CfRole())->getAllRolesNames();

        if (!!sizeof(($nonExistingRoles = array_diff($roles, $existingRoles)))) {
            throw new InvalidArgumentException('Specified roles do not exist: ' . join(', ', $nonExistingRoles));
        }

        return true;
    }

    public static function timezone(string $timezone): bool
    {
        if (!Utils::isTimeZone(CfPdo::getInstance()->getConnection(), $timezone)) {
            throw new InvalidArgumentException('Invalid `time_zone` value: ' . $timezone);
        }

        return true;
    }

    public static function date(string $date, string $label, array $formats = ['Y-m-d', 'Y-m-d H:i:s']): bool
    {
        // Loop through formats and check if any matches the input date
        foreach ($formats as $format) {
            $d = DateTime::createFromFormat($format, $date);
            if ($d && $d->format($format) === $date) {
                return true;
            }
        }

        throw new InvalidArgumentException("Invalid `$label` date format.");
    }

    public static function required(StdClass|array $data, string|array $key): bool
    {
        $validate = function ($key) use ($data) {
            if (is_array($data)) {
                $exists = !empty($data[$key]);
            } else {
                $exists = !empty($data->{$key});
            }

            if (!$exists) {
                throw new InvalidArgumentException(ucfirst($key) . ' is required.');
            }
        };

        if (is_array($key)) {
            array_walk_recursive($key, $validate, $key);
        } else {
            $validate($key);
        }

        return true;
    }

    public static function alphanumeric(string $value, string $label): bool
    {
        $pattern = '/^[a-zA-Z0-9]+$/';
        if (!preg_match($pattern, $value)) {
            throw new InvalidArgumentException($label . ' is not a valid alphanumeric string.');
        }

        return true;
    }

    public static function printableAscii(string $value, string $label): bool
    {
        if (!preg_match('/^[\x20-\x7E]+$/', $value)) {
            throw new InvalidArgumentException("$label contains invalid characters. Only printable ASCII characters are allowed.");
        }

        return true;
    }

    public static function url(string $url): bool
    {
        self::printableAscii($url, 'Url');

        if (strlen($url) > MAX_URL_LENGTH) {
            throw new InvalidArgumentException('Url is too long. Maximum allowed length is: ' . MAX_URL_LENGTH);
        }

        return true;
    }
}
