"""Tests for validators."""
from __future__ import annotations

import pytest

from repo_core import (
    ValidationError,
    BaseValidator,
    ValidatorPipeline,
    RequiredValidator,
    MaxLengthValidator,
)


class PositiveIntValidator(BaseValidator):
    async def validate(self, data: int) -> int:
        if data <= 0:
            raise ValidationError("Must be positive.", field="value")
        return data


class UpperCaseValidator(BaseValidator):
    async def validate(self, data: str) -> str:
        return data.upper()


# ---------------------------------------------------------------------------

async def test_base_validator_pass():
    v = PositiveIntValidator()
    result = await v(5)
    assert result == 5


async def test_base_validator_fail():
    v = PositiveIntValidator()
    with pytest.raises(ValidationError) as exc_info:
        await v(-1)
    assert exc_info.value.field == "value"
    assert "positive" in str(exc_info.value).lower()


async def test_validator_callable():
    v = UpperCaseValidator()
    result = await v("hello")
    assert result == "HELLO"


async def test_pipeline_passes_output_as_input():
    pipeline = ValidatorPipeline(UpperCaseValidator(), MaxLengthValidator(10))
    result = await pipeline.validate("hello")
    assert result == "HELLO"


async def test_pipeline_callable():
    pipeline = ValidatorPipeline(UpperCaseValidator())
    result = await pipeline("world")
    assert result == "WORLD"


async def test_pipeline_stops_on_first_error():
    class AlwaysFail(BaseValidator):
        async def validate(self, data):
            raise ValidationError("Always fails.")

    class ShouldNotRun(BaseValidator):
        async def validate(self, data):
            raise AssertionError("Should not be reached")

    pipeline = ValidatorPipeline(AlwaysFail(), ShouldNotRun())
    with pytest.raises(ValidationError, match="Always fails"):
        await pipeline.validate("x")


async def test_required_validator_rejects_none():
    v = RequiredValidator(field="name")
    with pytest.raises(ValidationError) as exc_info:
        await v(None)
    assert exc_info.value.field == "name"


async def test_required_validator_rejects_empty_string():
    v = RequiredValidator()
    with pytest.raises(ValidationError):
        await v("")


async def test_required_validator_passes_zero():
    v = RequiredValidator()
    result = await v(0)
    assert result == 0


async def test_max_length_validator_passes():
    v = MaxLengthValidator(10)
    result = await v("hello")
    assert result == "hello"


async def test_max_length_validator_rejects_long_string():
    v = MaxLengthValidator(5, field="title")
    with pytest.raises(ValidationError) as exc_info:
        await v("this is too long")
    assert exc_info.value.field == "title"


async def test_max_length_validator_passes_non_string():
    v = MaxLengthValidator(5)
    # Non-string values pass through unchanged
    result = await v(42)
    assert result == 42


def test_validation_error_str_with_field():
    err = ValidationError("bad value", field="email")
    assert "[email]" in str(err)
    assert "bad value" in str(err)


def test_validation_error_str_without_field():
    err = ValidationError("something went wrong")
    assert "something went wrong" in str(err)
