from __future__ import annotations

import os
import re
from pathlib import Path
from typing import Any, Callable
from urllib.parse import urlparse

Validator = Callable[[Any], tuple[bool, str]]


def required(value: Any) -> tuple[bool, str]:
    ok = bool(str(value).strip())
    return ok, '' if ok else 'This field is required.'


def min_length(length: int) -> Validator:
    def _inner(value: Any) -> tuple[bool, str]:
        ok = len(str(value).strip()) >= length
        return ok, '' if ok else f'Must be at least {length} characters.'
    return _inner


def int_field(value: Any) -> tuple[bool, str]:
    try:
        int(str(value).strip())
        return True, ''
    except Exception:
        return False, 'Must be an integer.'


def float_field(value: Any) -> tuple[bool, str]:
    try:
        float(str(value).strip())
        return True, ''
    except Exception:
        return False, 'Must be a float.'


def email_field(value: Any) -> tuple[bool, str]:
    ok = bool(re.match(r'^[^@\s]+@[^@\s]+\.[^@\s]+$', str(value).strip()))
    return ok, '' if ok else 'Must be a valid email.'


def url_field(value: Any) -> tuple[bool, str]:
    raw = str(value).strip()
    parsed = urlparse(raw)
    ok = bool(parsed.scheme and parsed.netloc)
    return ok, '' if ok else 'Must be a valid URL.'


def path_field(value: Any) -> tuple[bool, str]:
    raw = str(value).strip()
    ok = bool(raw) and (Path(raw).exists() or os.path.isabs(raw) or raw.startswith('.'))
    return ok, '' if ok else 'Must be a valid path or path-like value.'


def choice_field(options: list[str]) -> Validator:
    def _inner(value: Any) -> tuple[bool, str]:
        ok = str(value) in options
        return ok, '' if ok else f'Must be one of: {", ".join(options)}'
    return _inner


TYPE_VALIDATORS = {
    'int': int_field,
    'float': float_field,
    'email': email_field,
    'url': url_field,
    'path': path_field,
}


def validator_for(field_type: str | None) -> Validator | None:
    if not field_type:
        return None
    return TYPE_VALIDATORS.get(field_type)


class SchemaValidator:
    def __init__(self, schema: dict[str, list[Validator]]):
        self.schema = schema

    def validate(self, payload: dict[str, Any]) -> tuple[bool, dict[str, str]]:
        errors: dict[str, str] = {}
        for name, validators in self.schema.items():
            for validator in validators:
                ok, message = validator(payload.get(name))
                if not ok:
                    errors[name] = message
                    break
        return not errors, errors
