<?php
namespace App\Core\Service;
use App\Core\Service\Logger;

class Validator
{
    protected static $rules  = [];
    protected static $data   = [];
    protected static $errors = [];

    public static function setRules(array $rules)
    {
        self::$rules = $rules;
    }

    public static function setData(array $data)
    {
        self::$data = $data;
    }

    public static function getErrors(): array
    {
        return self::$errors;
    }

    public static function getFirstError(): string|NULL
    {
        foreach(self::$errors as $fld => $error) {
            return $error;
        }
        return NULL;
    }

    public static function required($value): bool
    {
        return !empty($value);
    }

    public static function email($value): bool
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }

    public static function minLength($value, $min): bool
    {
        return strlen($value) >= $min;
    }

    public static function maxLength($value, $max): bool
    {
        return strlen($value) <= $max;
    }

    public static function validIp($value): bool
    {
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
    }

    public static function validIps($value): bool
    {
        $ips = explode(',', $value);
        foreach ($ips as $ip) {
            if (!self::validIp(trim($ip))) {
                return false;
            }
        }
        return true;
    }

    public static function validEmails($value): bool
    {
        $emails = explode(',', $value);
        foreach ($emails as $email) {
            if (!self::email(trim($email))) {
                return false;
            }
        }
        return true;
    }
    
    public static function phone($value):bool 
    {
        if (!self::numeric(trim($value))) {
            return false;
        } else if(strlen(trim($value)) < 10) {
            return false;
        }
        return true;
    }

    public static function validPhones($value): bool
    {
        $phones = explode(',', $value);
        foreach ($phones as $no) {
            if (!self::phone(trim($no))) {
                return false;
            }
        }
        return true;
    }

    public static function validUrl($value): bool
    {
        return filter_var($value, FILTER_VALIDATE_URL) !== false;
    }

    public static function isNaturalNoZero($value): bool
    {
        return (int)$value > 0;
    }

    public static function isNatural($value): bool
    {
        return (int)$value >= 0;
    }

    public static function decimal($value): bool
    {
        return is_numeric($value) && preg_match('/^\d+(\.\d+)?$/', $value);
    }

    public static function integer($value): bool
    {
        return filter_var($value, FILTER_VALIDATE_INT) !== false;
    }

    public static function alphaNumericSpaces($value): bool
    {
        return preg_match('/^[a-zA-Z0-9 ]+$/', $value);
    }

    public static function numeric($value):bool 
    {
        return preg_match('/^[0-9]+$/', $value);
    }

    public static function lessThanEqualTo($value, $max): bool
    {
        return is_numeric($value) && $value <= $max;
    }

    public static function lessThan($value, $max): bool
    {
        return is_numeric($value) && $value < $max;
    }

    public static function greaterThan($value, $min): bool
    {
        return is_numeric($value) && $value > $min;
    }
    
    public static function greaterThanEqualTo($value, $min): bool
    {
        return is_numeric($value) && $value >= $min;
    }

    public static function inList($value, $list): bool
    {
        $listArray = explode(',', $list);
        return in_array($value, $listArray);
    }

    public static function notInList($value, $list): bool
    {
        $listArray = explode(',', $list);
        return !in_array($value, $listArray);
    }

    public static function isUnique($value, $table, $column, $excludeId = null): bool
    {
        return true;
    }

    public static function isNotUnique($value, $table, $column, $excludeId = null): bool
    {
        return false;
    }

    public static function isValidDate($value, $format = 'Y-m-d'): bool
    {
        $d = \DateTime::createFromFormat($format, $value);
        return $d && $d->format($format) === $value;
    }

    public static function isValidTime($value, $format = 'H:i:s'): bool
    {
        $d = \DateTime::createFromFormat($format, $value);
        return $d && $d->format($format) === $value;
    }

    public static function isValidDateTime($value, $format = 'Y-m-d H:i:s'): bool
    {
        $d = \DateTime::createFromFormat($format, $value);
        return $d && $d->format($format) === $value;
    }

    public static function isValidJson($value): bool
    {
        json_decode($value);
        return (json_last_error() === JSON_ERROR_NONE);
    }

    public static function run(): array
    {
        try {
            if (empty(self::$rules)) {
                throw new \Exception("Validation rules are not defined");
            }

            if (empty(self::$data)) {
                if (!empty($_REQUEST)) {
                    self::$data = $_REQUEST;
                } else {
                    throw new \Exception("Data for validations are not defined");
                }
            }

            self::$errors = [];
            $resp = self::validate();

            if (empty($resp['status'])) {
                throw new \Exception($resp['error']);
            }

            if (!empty(self::$errors)) {
                return [
                    "status" => true,
                    "valid" => false,
                    "validation_error" => self::$errors
                ];
            }

            return ["status" => true, "valid" => true];
        } catch (\Exception $e) {
            Logger::error($e->getMessage(), ['File' => $e->getFile(), 'Line' => $e->getLine()]);
            return ["status" => false, "error" => $e->getMessage()];
        }
    }

    private static function validate(): array
    {
        try {
            foreach (self::$rules as $field => $detail) {
                if (!is_array($detail) || !isset($detail['rules']) || !isset($detail['label'])) {
                    throw new \Exception("Each validation rule must include 'rules' and 'label' for field: $field");
                }

                $rules = explode('|', $detail['rules']);
                $label = $detail['label'];
                $value = self::$data[$field] ?? null;

                foreach ($rules as $rule) {
                    if ($rule === 'required') {
                        if (!array_key_exists($field, self::$data) || !self::required($value)) {
                            self::$errors[$field] = "$label is required";
                            break;
                        }
                    } elseif ($rule === 'valid_json') {
                        if (!empty($value) && !self::isValidJson($value)) {
                            self::$errors[$field] = "$label must be a valid JSON string";
                            break;
                        }
                    } elseif ($rule === 'valid_email' || $rule === 'email') {
                        if (!empty($value) && !self::email($value)) {
                            self::$errors[$field] = "$label must be a valid email ID";
                            break;
                        }
                    } elseif (preg_match('/min_length\[(\d+)\]/', $rule, $matches)) {
                        $min = (int)$matches[1];
                        if (!empty($value) && !self::minLength($value, $min)) {
                            self::$errors[$field] = "Minimum length of $label must be $min";
                            break;
                        }
                    } elseif (preg_match('/max_length\[(\d+)\]/', $rule, $matches)) {
                        $max = (int)$matches[1];
                        if (!empty($value) && !self::maxLength($value, $max)) {
                            self::$errors[$field] = "Maximum length of $label must be $max";
                            break;
                        }
                    } elseif ($rule === 'valid_ip') {
                        if (!empty($value) && !self::validIp($value)) {
                            self::$errors[$field] = "$label must be a valid IP address";
                            break;
                        }
                    } elseif ($rule === 'valid_ips') {
                        if (!empty($value) && !self::validIps($value)) {
                            self::$errors[$field] = "$label must be a valid IP address";
                            break;
                        }
                    } elseif ($rule === 'valid_emails') {
                        if (!empty($value) && !self::validEmails($value)) {
                            self::$errors[$field] = "$label must be a list of valid email addresses";
                            break;
                        }
                    } elseif ($rule === 'valid_phone' || $rule === 'phone') {
                        if (!empty($value) && !self::phone($value)) {
                            self::$errors[$field] = "$label must be a valid Phone No";
                            break;
                        }
                    } elseif ($rule === 'valid_phones') {
                        if (!empty($value) && !self::validPhones($value)) {
                            self::$errors[$field] = "$label must be a list of valid Phone Numbers";
                            break;
                        }
                    } elseif ($rule === 'valid_url') {
                        if (!empty($value) && !self::validUrl($value)) {
                            self::$errors[$field] = "$label must be a valid URL";
                            break;
                        }
                    } elseif ($rule === 'is_natural_no_zero') {
                        if (!empty($value) && !self::isNaturalNoZero($value)) {
                            self::$errors[$field] = "$label must be a natural number greater than zero";
                            break;
                        }
                    } elseif ($rule === 'is_natural') {
                        if (!empty($value) && !self::isNatural($value)) {
                            self::$errors[$field] = "$label must be a natural number (zero or more)";
                            break;
                        }
                    } elseif ($rule === 'decimal') {
                        if (!empty($value) && !self::decimal($value)) {
                            self::$errors[$field] = "$label must be a decimal number";
                            break;
                        }
                    } elseif ($rule === 'integer') {
                        if (!empty($value) && !self::integer($value)) {
                            self::$errors[$field] = "$label must be an integer";
                            break;
                        }
                    } elseif ($rule === 'alpha_numeric_spaces') {
                        if (!empty($value) && !self::alphaNumericSpaces($value)) {
                            self::$errors[$field] = "$label may only contain letters, numbers, and spaces";
                            break;
                        }
                    } elseif (preg_match('/less_than_equal_to\[(\d+)\]/', $rule, $matches)) {
                        $max = (int)$matches[1];
                        if (!empty($value) && !self::lessThanEqualTo($value, $max)) {
                            self::$errors[$field] = "$label must be less than or equal to $max";
                            break;
                        }
                    } elseif (preg_match('/less_than\[(\d+)\]/', $rule, $matches)) {
                        $max = (int)$matches[1];
                        if (!empty($value) && !self::lessThan($value, $max)) {
                            self::$errors[$field] = "$label must be less than $max";
                            break;
                        }
                    } elseif (preg_match('/greater_than\[(\d+)\]/', $rule, $matches)) {
                        $min = (int)$matches[1];
                        if (!empty($value) && !self::greaterThan($value, $min)) {
                            self::$errors[$field] = "$label must be greater than $min";
                            break;
                        }
                    } elseif (preg_match('/greater_than_equal_to\[(\d+)\]/', $rule, $matches)) {
                        $min = (int)$matches[1];
                        if (!empty($value) && !self::greaterThanEqualTo($value, $min)) {
                            self::$errors[$field] = "$label must be greater than or equal to $min";
                            break;
                        }
                    } elseif (preg_match('/in_list\[(.+)\]/', $rule, $matches)) {
                        if (!empty($value) && !self::inList($value, $matches[1])) {
                            self::$errors[$field] = "$label must be one of: " . implode(', ', explode(',', $matches[1]));
                            break;
                        }
                    } elseif (preg_match('/not_in_list\[(.+)\]/', $rule, $matches)) {
                        if (!empty($value) && !self::notInList($value, $matches[1])) {
                            self::$errors[$field] = "$label must not be one of: " . implode(', ', explode(',', $matches[1]));
                            break;
                        }
                    } elseif (preg_match('/is_unique\[(.+),(.+)(?:,(.+))?\]/', $rule, $matches)) {
                        if (!self::isUnique($value, trim($matches[1]), trim($matches[2]), isset($matches[3]) ? trim($matches[3]) : null)) {
                            self::$errors[$field] = "$label must be unique in " . trim($matches[1]) . " table";
                            break;
                        }
                    } elseif (preg_match('/is_not_unique\[(.+),(.+)(?:,(.+))?\]/', $rule, $matches)) {
                        if (self::isUnique($value, trim($matches[1]), trim($matches[2]), isset($matches[3]) ? trim($matches[3]) : null)) {
                            self::$errors[$field] = "$label must not be unique in " . trim($matches[1]) . " table";
                            break;
                        }
                    } elseif (preg_match('/valid_date\[(.+)\]/', $rule, $matches)) {
                        if (!self::isValidDate($value, $matches[1])) {
                            self::$errors[$field] = "$label must be a valid date in format " . $matches[1];
                            break;
                        }
                    } elseif (preg_match('/valid_time\[(.+)\]/', $rule, $matches)) {
                        if (!self::isvalidTime($value, $matches[1])) {
                            self::$errors[$field] = "$label must be a valid time in format " . $matches[1];
                            break;
                        }
                    }
                }
            }
            return ["status" => true];
        } catch (\Exception $e) {
            Logger::error($e->getMessage(), ['File' => $e->getFile(), 'Line' => $e->getLine()]);
            return ["status" => false, "error" => $e->getMessage()];
        }
    }
}
