"""Edge-case tests for validators and pipelines."""

from __future__ import annotations

import pytest

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


# ===========================================================================
# ValidationError edge cases
# ===========================================================================

def test_validation_error_repr():
    err = ValidationError("bad input", field="email")
    r = repr(err)
    assert "ValidationError" in r
    assert "bad input" in r


def test_validation_error_field_none_str():
    err = ValidationError("generic error")
    assert str(err) == "generic error"


def test_validation_error_field_set_str():
    err = ValidationError("error", field="username")
    assert "[username]" in str(err)
    assert "error" in str(err)


# ===========================================================================
# ValidatorPipeline edge cases
# ===========================================================================

async def test_pipeline_empty_no_validators():
    """An empty pipeline should return data unchanged."""
    pipeline = ValidatorPipeline()
    result = await pipeline.validate(42)
    assert result == 42


async def test_pipeline_empty_callable():
    pipeline = ValidatorPipeline()
    result = await pipeline("hello")
    assert result == "hello"


async def test_pipeline_with_transformers():
    """Pipeline passes output of each validator as input to the next."""

    class AddOne(BaseValidator):
        async def validate(self, data: int) -> int:
            return data + 1

    class Double(BaseValidator):
        async def validate(self, data: int) -> int:
            return data * 2

    pipeline = ValidatorPipeline(AddOne(), Double(), AddOne())
    result = await pipeline.validate(3)  # ((3+1)*2)+1 = 9
    assert result == 9


async def test_pipeline_handles_different_types():
    """Pipeline can transform types along the way."""

    class IntToStr(BaseValidator):
        async def validate(self, data: int) -> str:
            return str(data)

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

    pipeline = ValidatorPipeline(IntToStr(), StrToUpper())
    result = await pipeline.validate(42)
    assert result == "42"
    assert isinstance(result, str)


# ===========================================================================
# RequiredValidator edge cases
# ===========================================================================

async def test_required_validator_passes_non_empty_string():
    v = RequiredValidator()
    result = await v("hello")
    assert result == "hello"


async def test_required_validator_passes_boolean_false():
    v = RequiredValidator()
    result = await v(False)
    assert result is False


async def test_required_validator_passes_empty_list():
    v = RequiredValidator()
    result = await v([])
    assert result == []


async def test_required_validator_passes_empty_dict():
    v = RequiredValidator()
    result = await v({})
    assert result == {}


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


# ===========================================================================
# MaxLengthValidator edge cases
# ===========================================================================

async def test_max_length_validator_at_boundary():
    v = MaxLengthValidator(5)
    result = await v("hello")  # exactly 5 chars
    assert result == "hello"


async def test_max_length_validator_passes_none():
    v = MaxLengthValidator(10)
    # Non-string values pass through
    result = await v(None)
    assert result is None


async def test_max_length_validator_passes_bool():
    v = MaxLengthValidator(10)
    result = await v(True)
    assert result is True


async def test_max_length_validator_passes_float():
    v = MaxLengthValidator(10)
    result = await v(3.14)
    assert result == 3.14


async def test_max_length_validator_one_over_limit():
    v = MaxLengthValidator(3)
    with pytest.raises(ValidationError):
        await v("four")


# ===========================================================================
# Custom validator patterns
# ===========================================================================

async def test_validator_can_raise_without_field():
    class NoFieldValidator(BaseValidator):
        async def validate(self, data):
            if data == "bad":
                raise ValidationError("Value is bad")
            return data

    v = NoFieldValidator()
    with pytest.raises(ValidationError) as exc_info:
        await v("bad")
    assert exc_info.value.field is None
    assert "Value is bad" in str(exc_info.value)


async def test_validator_can_return_different_type():
    class ListToLen(BaseValidator):
        async def validate(self, data: list) -> int:
            return len(data)

    v = ListToLen()
    result = await v([1, 2, 3])
    assert result == 3
    assert isinstance(result, int)


async def test_multiple_pipelines_independent():
    """Pipelines don't interfere with each other."""
    p1 = ValidatorPipeline(MaxLengthValidator(3))
    p2 = ValidatorPipeline(MaxLengthValidator(20))

    await p1.validate("abc")  # passes
    with pytest.raises(ValidationError):
        await p1.validate("too long for p1")

    # p2 should still work with a longer string
    result = await p2.validate("longer text here")
    assert result == "longer text here"
