<?php
/**
 * Universal Form & API Input Validation Engine
 * Comprehensive Rules: Type, Format, Length, Range, Allowed Values, Existence, Thai ID Checksum, Database Checks
 */

require_once __DIR__ . '/../../database/Database.php';
require_once __DIR__ . '/../Core/Exceptions/ValidationException.php';

class Validator {
    private array $data;
    private array $rules;
    private array $errors = [];
    private array $validatedData = [];

    public function __construct(array $data, array $rules) {
        $this->data = $data;
        $this->rules = $rules;
    }

    public static function make(array $data, array $rules): self {
        return new self($data, $rules);
    }

    /**
     * Validate and throw ValidationException on failure, or return validated data
     */
    public static function validateOrFail(array $data, array $rules): array {
        $validator = new self($data, $rules);
        if (!$validator->validate()) {
            throw new ValidationException($validator->errors());
        }
        return $validator->validated();
    }

    /**
     * Sanitize input by only keeping allowed fields and stripping harmful tags
     */
    public static function sanitize(array $data, array $allowedFields = []): array {
        $cleaned = [];
        $targetData = !empty($allowedFields) ? array_intersect_key($data, array_flip($allowedFields)) : $data;

        foreach ($targetData as $key => $val) {
            if (is_string($val)) {
                $cleaned[$key] = trim(strip_tags($val));
            } elseif (is_array($val)) {
                $cleaned[$key] = self::sanitize($val);
            } else {
                $cleaned[$key] = $val;
            }
        }
        return $cleaned;
    }

    public function validate(): bool {
        $this->errors = [];
        $this->validatedData = [];

        foreach ($this->rules as $field => $fieldRules) {
            $value = $this->data[$field] ?? null;
            $ruleList = is_string($fieldRules) ? explode('|', $fieldRules) : $fieldRules;

            foreach ($ruleList as $rule) {
                $params = [];
                if (strpos($rule, ':') !== false) {
                    [$ruleName, $paramStr] = explode(':', $rule, 2);
                    $params = explode(',', $paramStr);
                } else {
                    $ruleName = $rule;
                }

                $this->applyRule($field, $value, $ruleName, $params);
            }

            if (!isset($this->errors[$field])) {
                $this->validatedData[$field] = $value;
            }
        }

        return empty($this->errors);
    }

    private function applyRule(string $field, $value, string $rule, array $params): void {
        // If field is optional and empty, skip further rules unless rule is required
        if ($rule !== 'required' && ($value === null || $value === '' || (is_array($value) && empty($value)))) {
            return;
        }

        switch ($rule) {
            // ── Required & Nullability ───────────────────────────────
            case 'required':
                if ($value === null || (is_string($value) && trim($value) === '') || (is_array($value) && empty($value))) {
                    $this->addError($field, "กรุณากรอกข้อมูล {$field}");
                }
                break;

            case 'nullable':
                // Handled implicitly by the early return
                break;

            // ── Data Types ───────────────────────────────────────────
            case 'numeric':
                if (!is_numeric($value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวเลข");
                }
                break;

            case 'integer':
            case 'int':
                if (filter_var($value, FILTER_VALIDATE_INT) === false) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นจำนวนเต็ม");
                }
                break;

            case 'float':
            case 'decimal':
                if (filter_var($value, FILTER_VALIDATE_FLOAT) === false) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวเลขทศนิยม");
                }
                break;

            case 'boolean':
            case 'bool':
                if (!in_array($value, [true, false, 1, 0, '1', '0', 'true', 'false'], true)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นค่าจริงหรือเท็จ (boolean)");
                }
                break;

            case 'array':
                if (!is_array($value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นชุดข้อมูล (array)");
                }
                break;

            case 'string':
                if (!is_string($value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นข้อความ");
                }
                break;

            case 'json':
                if (!is_string($value) || json_decode($value) === null && json_last_error() !== JSON_ERROR_NONE) {
                    $this->addError($field, "ข้อมูล {$field} ต้องอยู่ในรูปแบบ JSON ที่ถูกต้อง");
                }
                break;

            // ── Formats & Regex ──────────────────────────────────────
            case 'email':
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $this->addError($field, "รูปแบบอีเมลไม่ถูกต้อง");
                }
                break;

            case 'phone':
                // Thai 10-digit mobile/tel: 08x, 09x, 06x, 02x
                $cleanPhone = preg_replace('/[^0-9]/', '', (string)$value);
                if (!preg_match('/^0[0-9]{9}$/', $cleanPhone)) {
                    $this->addError($field, "เบอร์โทรศัพท์ต้องเป็นตัวเลข 10 หลักและขึ้นต้นด้วย 0");
                }
                break;

            case 'thai_id':
                // Thai 13-digit National ID Checksum Algorithm (Mod 11)
                $cleanId = preg_replace('/[^0-9]/', '', (string)$value);
                if (strlen($cleanId) !== 13) {
                    $this->addError($field, "เลขบัตรประชาชนต้องเป็นตัวเลข 13 หลัก");
                } else {
                    $sum = 0;
                    for ($i = 0; $i < 12; $i++) {
                        $sum += ((int)$cleanId[$i]) * (13 - $i);
                    }
                    $check = (11 - ($sum % 11)) % 10;
                    if ($check !== (int)$cleanId[12]) {
                        $this->addError($field, "เลขบัตรประชาชนไม่ถูกต้องตามรูปแบบทางการ");
                    }
                }
                break;

            case 'url':
                if (!filter_var($value, FILTER_VALIDATE_URL)) {
                    $this->addError($field, "รูปแบบ URL ไม่ถูกต้อง");
                }
                break;

            case 'slug':
                if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', (string)$value)) {
                    $this->addError($field, "สลัก (slug) ต้องประกอบด้วยตัวอักษรพิมพ์เล็ก ตัวเลข และเครื่องหมาย - เท่านั้น");
                }
                break;

            case 'postal_code':
                if (!preg_match('/^[0-9]{5}$/', (string)$value)) {
                    $this->addError($field, "รหัสไปรษณีย์ต้องเป็นตัวเลข 5 หลัก");
                }
                break;

            case 'username':
                if (!preg_match('/^[a-zA-Z0-9_]{3,30}$/', (string)$value)) {
                    $this->addError($field, "ชื่อผู้ใช้ต้องเป็นตัวอักษร ตัวเลข หรือ _ ความยาว 3-30 ตัวอักษร");
                }
                break;

            case 'alpha':
                if (!preg_match('/^[\p{L}]+$/u', (string)$value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษรเท่านั้น");
                }
                break;

            case 'alpha_num':
                if (!preg_match('/^[\p{L}0-9]+$/u', (string)$value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษรหรือตัวเลขเท่านั้น");
                }
                break;

            case 'alpha_dash':
                if (!preg_match('/^[\p{L}0-9_-]+$/u', (string)$value)) {
                    $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษร ตัวเลข _ หรือ - เท่านั้น");
                }
                break;

            case 'regex':
                $pattern = $params[0] ?? '//';
                if (!preg_match($pattern, (string)$value)) {
                    $this->addError($field, "ข้อมูล {$field} ไม่ตรงตามรูปแบบที่กำหนด");
                }
                break;

            // ── Ranges & Comparisons ─────────────────────────────────
            case 'min':
                $min = (float)($params[0] ?? 0);
                if (is_numeric($value)) {
                    if ((float)$value < $min) {
                        $this->addError($field, "ค่าต้องไม่น้อยกว่า {$min}");
                    }
                } elseif (is_string($value)) {
                    if (mb_strlen($value) < (int)$min) {
                        $this->addError($field, "ความยาวต้องไม่น้อยกว่า {$min} ตัวอักษร");
                    }
                } elseif (is_array($value)) {
                    if (count($value) < (int)$min) {
                        $this->addError($field, "ต้องมีรายการอย่างน้อย {$min} รายการ");
                    }
                }
                break;

            case 'max':
                $max = (float)($params[0] ?? 255);
                if (is_numeric($value)) {
                    if ((float)$value > $max) {
                        $this->addError($field, "ค่าต้องไม่เกิน {$max}");
                    }
                } elseif (is_string($value)) {
                    if (mb_strlen($value) > (int)$max) {
                        $this->addError($field, "ความยาวต้องไม่เกิน {$max} ตัวอักษร");
                    }
                } elseif (is_array($value)) {
                    if (count($value) > (int)$max) {
                        $this->addError($field, "ต้องมีรายการไม่เกิน {$max} รายการ");
                    }
                }
                break;

            case 'between':
                $min = (float)($params[0] ?? 0);
                $max = (float)($params[1] ?? 0);
                if (is_numeric($value)) {
                    if ((float)$value < $min || (float)$value > $max) {
                        $this->addError($field, "ค่าต้องอยู่ระหว่าง {$min} ถึง {$max}");
                    }
                } else {
                    $len = mb_strlen((string)$value);
                    if ($len < (int)$min || $len > (int)$max) {
                        $this->addError($field, "ความยาวต้องอยู่ระหว่าง {$min} ถึง {$max} ตัวอักษร");
                    }
                }
                break;

            case 'gt':
                $target = (float)($params[0] ?? 0);
                if ((float)$value <= $target) {
                    $this->addError($field, "ค่าต้องมากกว่า {$target}");
                }
                break;

            case 'gte':
                $target = (float)($params[0] ?? 0);
                if ((float)$value < $target) {
                    $this->addError($field, "ค่าต้องมากกว่าหรือเท่ากับ {$target}");
                }
                break;

            case 'lt':
                $target = (float)($params[0] ?? 0);
                if ((float)$value >= $target) {
                    $this->addError($field, "ค่าต้องน้อยกว่า {$target}");
                }
                break;

            case 'lte':
                $target = (float)($params[0] ?? 0);
                if ((float)$value > $target) {
                    $this->addError($field, "ค่าต้องน้อยกว่าหรือเท่ากับ {$target}");
                }
                break;

            // ── Whitelisting & In/Not In ──────────────────────────────
            case 'in':
                if (!in_array((string)$value, $params, true)) {
                    $this->addError($field, "ค่า {$field} ต้องเป็นหนึ่งใน: " . implode(', ', $params));
                }
                break;

            case 'not_in':
                if (in_array((string)$value, $params, true)) {
                    $this->addError($field, "ค่า {$field} ไม่อยู่ในรายการที่อนุญาต");
                }
                break;

            // ── Database Uniqueness & Existence ───────────────────────
            case 'unique':
                // format: unique:table,column[,ignore_id,ignore_column]
                $table = $params[0] ?? 'users';
                $column = $params[1] ?? $field;
                $ignoreId = $params[2] ?? null;
                $ignoreCol = $params[3] ?? 'id';

                $db = Database::getInstance();
                $sql = "SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` = :val";
                $bindings = [':val' => $value];

                if ($ignoreId !== null && $ignoreId !== '') {
                    $sql .= " AND `{$ignoreCol}` != :ignore_id";
                    $bindings[':ignore_id'] = $ignoreId;
                }

                $stmt = $db->prepare($sql);
                $stmt->execute($bindings);
                if ((int)$stmt->fetchColumn() > 0) {
                    $this->addError($field, "ข้อมูล {$field} นี้ถูกใช้งานในระบบแล้ว");
                }
                break;

            case 'exists':
                // format: exists:table,column
                $table = $params[0] ?? 'users';
                $column = $params[1] ?? 'id';

                $db = Database::getInstance();
                $sql = "SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` = :val";
                $stmt = $db->prepare($sql);
                $stmt->execute([':val' => $value]);
                if ((int)$stmt->fetchColumn() === 0) {
                    $this->addError($field, "ไม่พบข้อมูลที่อ้างอิงในระบบ");
                }
                break;

            // ── Confirmation & Equality ──────────────────────────────
            case 'same':
            case 'matches':
                $targetField = $params[0] ?? '';
                $targetVal = $this->data[$targetField] ?? null;
                if ($value !== $targetVal) {
                    $this->addError($field, "ข้อมูล {$field} ไม่ตรงกับ {$targetField}");
                }
                break;

            case 'confirmed':
                $confirmField = $field . '_confirmation';
                $confirmVal = $this->data[$confirmField] ?? null;
                if ($value !== $confirmVal) {
                    $this->addError($field, "การยืนยันรหัสผ่านไม่ตรงกัน");
                }
                break;

            case 'different':
                $targetField = $params[0] ?? '';
                $targetVal = $this->data[$targetField] ?? null;
                if ($value === $targetVal) {
                    $this->addError($field, "ข้อมูล {$field} ต้องแตกต่างจาก {$targetField}");
                }
                break;

            case 'accepted':
                if (!in_array($value, [1, '1', true, 'true', 'on', 'yes'], true)) {
                    $this->addError($field, "คุณต้องกดยอมรับเงื่อนไขก่อนดำเนินการ");
                }
                break;

            // ── Date Rules ───────────────────────────────────────────
            case 'date':
                if (strtotime((string)$value) === false) {
                    $this->addError($field, "วันที่ไม่ถูกต้อง");
                }
                break;

            case 'date_format':
                $format = $params[0] ?? 'Y-m-d';
                $d = DateTime::createFromFormat($format, (string)$value);
                if (!$d || $d->format($format) !== (string)$value) {
                    $this->addError($field, "รูปแบบวันที่ต้องเป็น {$format}");
                }
                break;

            case 'after':
                $targetDate = strtotime($params[0] ?? 'now');
                $valDate = strtotime((string)$value);
                if ($valDate === false || $valDate <= $targetDate) {
                    $this->addError($field, "วันที่ต้องอยู่หลังจาก " . date('Y-m-d', $targetDate));
                }
                break;

            case 'before':
                $targetDate = strtotime($params[0] ?? 'now');
                $valDate = strtotime((string)$value);
                if ($valDate === false || $valDate >= $targetDate) {
                    $this->addError($field, "วันที่ต้องอยู่ก่อน " . date('Y-m-d', $targetDate));
                }
                break;
        }
    }

    private function addError(string $field, string $message): void {
        if (!isset($this->errors[$field])) {
            $this->errors[$field] = $message;
        }
    }

    public function errors(): array {
        return $this->errors;
    }

    public function firstError(): ?string {
        return reset($this->errors) ?: null;
    }

    public function validated(): array {
        return $this->validatedData;
    }
}
