"""
Deep behavioral tests for input validation patterns.
Tests field validation, schema validation, sanitization,
and constraint checking logic.
"""

import re
from datetime import datetime, timezone


# --- String validation patterns ---


class TestStringValidation:
    """Tests for string validation patterns."""

    def _is_non_empty(self, value):
        """Check if string is non-empty."""
        return isinstance(value, str) and len(value.strip()) > 0

    def _has_min_length(self, value, min_len):
        """Check minimum length."""
        return isinstance(value, str) and len(value) >= min_len

    def _has_max_length(self, value, max_len):
        """Check maximum length."""
        return isinstance(value, str) and len(value) <= max_len

    def _matches_pattern(self, value, pattern):
        """Check if value matches regex pattern."""
        if not isinstance(value, str):
            return False
        return bool(re.match(pattern, value))

    def test_non_empty_with_content(self):
        assert self._is_non_empty("hello") is True

    def test_non_empty_with_whitespace(self):
        assert self._is_non_empty("   ") is False

    def test_non_empty_with_empty(self):
        assert self._is_non_empty("") is False

    def test_min_length_valid(self):
        assert self._has_min_length("hello", 3) is True

    def test_min_length_too_short(self):
        assert self._has_min_length("hi", 3) is False

    def test_max_length_valid(self):
        assert self._has_max_length("hello", 10) is True

    def test_max_length_too_long(self):
        assert self._has_max_length("hello world", 5) is False

    def test_pattern_match(self):
        assert self._matches_pattern("abc123", r"^[a-z]+\d+$") is True

    def test_pattern_no_match(self):
        assert self._matches_pattern("123abc", r"^[a-z]+\d+$") is False


class TestEmailValidation:
    """Tests for email validation patterns."""

    def _is_valid_email(self, email):
        """Validate email format."""
        if not isinstance(email, str):
            return False
        pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
        return bool(re.match(pattern, email))

    def _extract_domain(self, email):
        """Extract domain from email."""
        if "@" not in email:
            return None
        return email.split("@")[1]

    def test_valid_email(self):
        assert self._is_valid_email("user@example.com") is True

    def test_valid_email_with_dots(self):
        assert self._is_valid_email("user.name@example.com") is True

    def test_invalid_no_at(self):
        assert self._is_valid_email("userexample.com") is False

    def test_invalid_no_domain(self):
        assert self._is_valid_email("user@") is False

    def test_extract_domain(self):
        assert self._extract_domain("user@example.com") == "example.com"


class TestURLValidation:
    """Tests for URL validation patterns."""

    def _is_valid_url(self, url):
        """Validate URL format."""
        if not isinstance(url, str):
            return False
        pattern = r"^https?://[^\s/$.?#].[^\s]*$"
        return bool(re.match(pattern, url))

    def _is_https(self, url):
        """Check if URL uses HTTPS."""
        return url.startswith("https://")

    def _extract_host(self, url):
        """Extract host from URL."""
        match = re.match(r"https?://([^/]+)", url)
        return match.group(1) if match else None

    def test_valid_http(self):
        assert self._is_valid_url("http://example.com") is True

    def test_valid_https(self):
        assert self._is_valid_url("https://example.com/path") is True

    def test_invalid_no_protocol(self):
        assert self._is_valid_url("example.com") is False

    def test_is_https(self):
        assert self._is_https("https://example.com") is True
        assert self._is_https("http://example.com") is False

    def test_extract_host(self):
        assert self._extract_host("https://example.com/path") == "example.com"


# --- Numeric validation patterns ---


class TestNumericValidation:
    """Tests for numeric validation patterns."""

    def _is_positive(self, value):
        """Check if value is positive."""
        return isinstance(value, (int, float)) and value > 0

    def _is_non_negative(self, value):
        """Check if value is non-negative."""
        return isinstance(value, (int, float)) and value >= 0

    def _is_in_range(self, value, min_val, max_val):
        """Check if value is in range."""
        return isinstance(value, (int, float)) and min_val <= value <= max_val

    def _is_integer(self, value):
        """Check if value is an integer."""
        if isinstance(value, bool):
            return False
        return isinstance(value, int) or (
            isinstance(value, float) and value.is_integer()
        )

    def test_positive(self):
        assert self._is_positive(5) is True
        assert self._is_positive(-1) is False
        assert self._is_positive(0) is False

    def test_non_negative(self):
        assert self._is_non_negative(0) is True
        assert self._is_non_negative(5) is True
        assert self._is_non_negative(-1) is False

    def test_in_range(self):
        assert self._is_in_range(5, 1, 10) is True
        assert self._is_in_range(0, 1, 10) is False

    def test_is_integer(self):
        assert self._is_integer(5) is True
        assert self._is_integer(5.0) is True
        assert self._is_integer(5.5) is False


class TestPercentageValidation:
    """Tests for percentage validation patterns."""

    def _is_valid_percentage(self, value):
        """Check if value is a valid percentage (0-100)."""
        return isinstance(value, (int, float)) and 0 <= value <= 100

    def _is_valid_ratio(self, value):
        """Check if value is a valid ratio (0-1)."""
        return isinstance(value, (int, float)) and 0 <= value <= 1

    def _percentage_to_ratio(self, percentage):
        """Convert percentage to ratio."""
        return percentage / 100

    def test_valid_percentage(self):
        assert self._is_valid_percentage(50) is True
        assert self._is_valid_percentage(0) is True
        assert self._is_valid_percentage(100) is True

    def test_invalid_percentage(self):
        assert self._is_valid_percentage(-5) is False
        assert self._is_valid_percentage(101) is False

    def test_valid_ratio(self):
        assert self._is_valid_ratio(0.5) is True
        assert self._is_valid_ratio(0) is True
        assert self._is_valid_ratio(1) is True

    def test_percentage_to_ratio(self):
        assert self._percentage_to_ratio(50) == 0.5


# --- Date validation patterns ---


class TestDateValidation:
    """Tests for date validation patterns."""

    def _is_valid_date_string(self, value, format_str="%Y-%m-%d"):
        """Check if string is a valid date."""
        if not isinstance(value, str):
            return False
        try:
            datetime.strptime(value, format_str)
            return True
        except ValueError:
            return False

    def _is_future_date(self, dt):
        """Check if date is in the future."""
        if not isinstance(dt, datetime):
            return False
        now = datetime.now(timezone.utc)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt > now

    def _is_past_date(self, dt):
        """Check if date is in the past."""
        if not isinstance(dt, datetime):
            return False
        now = datetime.now(timezone.utc)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt < now

    def test_valid_date_string(self):
        assert self._is_valid_date_string("2025-06-15") is True

    def test_invalid_date_string(self):
        assert self._is_valid_date_string("2025-13-45") is False

    def test_invalid_format(self):
        assert self._is_valid_date_string("15/06/2025") is False

    def test_future_date(self):
        future = datetime(2030, 1, 1, tzinfo=timezone.utc)
        assert self._is_future_date(future) is True

    def test_past_date(self):
        past = datetime(2020, 1, 1, tzinfo=timezone.utc)
        assert self._is_past_date(past) is True


# --- Collection validation patterns ---


class TestCollectionValidation:
    """Tests for collection validation patterns."""

    def _is_non_empty_list(self, value):
        """Check if value is a non-empty list."""
        return isinstance(value, list) and len(value) > 0

    def _has_min_items(self, value, min_count):
        """Check minimum item count."""
        return isinstance(value, (list, tuple)) and len(value) >= min_count

    def _has_max_items(self, value, max_count):
        """Check maximum item count."""
        return isinstance(value, (list, tuple)) and len(value) <= max_count

    def _all_match_type(self, items, expected_type):
        """Check all items are of expected type."""
        return all(isinstance(item, expected_type) for item in items)

    def test_non_empty_list(self):
        assert self._is_non_empty_list([1, 2, 3]) is True
        assert self._is_non_empty_list([]) is False

    def test_min_items(self):
        assert self._has_min_items([1, 2, 3], 2) is True
        assert self._has_min_items([1], 2) is False

    def test_max_items(self):
        assert self._has_max_items([1, 2], 5) is True
        assert self._has_max_items([1, 2, 3, 4, 5, 6], 5) is False

    def test_all_match_type(self):
        assert self._all_match_type([1, 2, 3], int) is True
        assert self._all_match_type([1, "2", 3], int) is False


class TestUniqueValidation:
    """Tests for uniqueness validation patterns."""

    def _has_unique_items(self, items):
        """Check if all items are unique."""
        return len(items) == len(set(items))

    def _has_unique_field(self, items, field):
        """Check if field values are unique across items."""
        values = [item.get(field) for item in items]
        return len(values) == len(set(values))

    def _find_duplicates(self, items):
        """Find duplicate items."""
        seen = set()
        duplicates = []
        for item in items:
            if item in seen:
                duplicates.append(item)
            seen.add(item)
        return duplicates

    def test_all_unique(self):
        assert self._has_unique_items([1, 2, 3]) is True

    def test_has_duplicates(self):
        assert self._has_unique_items([1, 2, 2]) is False

    def test_unique_field(self):
        items = [{"id": 1}, {"id": 2}, {"id": 3}]
        assert self._has_unique_field(items, "id") is True

    def test_duplicate_field(self):
        items = [{"id": 1}, {"id": 2}, {"id": 1}]
        assert self._has_unique_field(items, "id") is False

    def test_find_duplicates(self):
        duplicates = self._find_duplicates([1, 2, 2, 3, 3, 3])
        assert 2 in duplicates
        assert 3 in duplicates


# --- Schema validation patterns ---


class TestSchemaValidation:
    """Tests for schema validation patterns."""

    def _validate_required_fields(self, data, required):
        """Validate required fields are present."""
        missing = [f for f in required if f not in data or data[f] is None]
        return len(missing) == 0, missing

    def _validate_field_types(self, data, type_schema):
        """Validate field types."""
        errors = []
        for field, expected_type in type_schema.items():
            if field in data and data[field] is not None:
                if not isinstance(data[field], expected_type):
                    errors.append(f"{field}: expected {expected_type.__name__}")
        return len(errors) == 0, errors

    def _validate_schema(self, data, schema):
        """Validate data against schema."""
        errors = []
        for field, rules in schema.items():
            value = data.get(field)
            if rules.get("required") and value is None:
                errors.append(f"{field}: required")
                continue
            if value is not None:
                if "type" in rules and not isinstance(value, rules["type"]):
                    errors.append(f"{field}: invalid type")
                if "min" in rules and value < rules["min"]:
                    errors.append(f"{field}: below minimum")
                if "max" in rules and value > rules["max"]:
                    errors.append(f"{field}: above maximum")
        return len(errors) == 0, errors

    def test_required_fields_present(self):
        data = {"name": "test", "email": "test@example.com"}
        valid, missing = self._validate_required_fields(data, ["name", "email"])
        assert valid is True

    def test_required_fields_missing(self):
        data = {"name": "test"}
        valid, missing = self._validate_required_fields(data, ["name", "email"])
        assert valid is False
        assert "email" in missing

    def test_field_types_valid(self):
        data = {"name": "test", "age": 25}
        valid, errors = self._validate_field_types(
            data, {"name": str, "age": int}
        )
        assert valid is True

    def test_field_types_invalid(self):
        data = {"name": "test", "age": "25"}
        valid, errors = self._validate_field_types(
            data, {"name": str, "age": int}
        )
        assert valid is False


# --- Sanitization patterns ---


class TestInputSanitization:
    """Tests for input sanitization patterns."""

    def _trim_whitespace(self, value):
        """Trim whitespace from string."""
        return value.strip() if isinstance(value, str) else value

    def _normalize_whitespace(self, value):
        """Normalize internal whitespace."""
        if not isinstance(value, str):
            return value
        return " ".join(value.split())

    def _remove_control_chars(self, value):
        """Remove control characters."""
        if not isinstance(value, str):
            return value
        return "".join(c for c in value if ord(c) >= 32 or c in "\n\t")

    def _truncate(self, value, max_length):
        """Truncate string to max length."""
        if not isinstance(value, str):
            return value
        return value[:max_length]

    def test_trim_whitespace(self):
        assert self._trim_whitespace("  hello  ") == "hello"

    def test_normalize_whitespace(self):
        assert self._normalize_whitespace("hello   world") == "hello world"

    def test_remove_control_chars(self):
        result = self._remove_control_chars("hello\x00world")
        assert "\x00" not in result

    def test_truncate(self):
        assert self._truncate("hello world", 5) == "hello"


class TestHTMLSanitization:
    """Tests for HTML sanitization patterns."""

    def _strip_html(self, value):
        """Remove all HTML tags."""
        if not isinstance(value, str):
            return value
        return re.sub(r"<[^>]+>", "", value)

    def _escape_html(self, value):
        """Escape HTML special characters."""
        if not isinstance(value, str):
            return value
        escapes = {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;"}
        for char, escape in escapes.items():
            value = value.replace(char, escape)
        return value

    def test_strip_html(self):
        result = self._strip_html("<p>Hello <b>World</b></p>")
        assert result == "Hello World"

    def test_escape_html(self):
        result = self._escape_html("<script>alert('xss')</script>")
        assert "<" not in result
        assert "&lt;" in result


# --- Constraint validation patterns ---


class TestConstraintValidation:
    """Tests for constraint validation patterns."""

    def _validate_not_before(self, value, min_date):
        """Validate date is not before minimum."""
        if not isinstance(value, datetime):
            return False
        return value >= min_date

    def _validate_not_after(self, value, max_date):
        """Validate date is not after maximum."""
        if not isinstance(value, datetime):
            return False
        return value <= max_date

    def _validate_one_of(self, value, allowed):
        """Validate value is one of allowed values."""
        return value in allowed

    def _validate_none_of(self, value, disallowed):
        """Validate value is not in disallowed values."""
        return value not in disallowed

    def test_not_before(self):
        min_date = datetime(2025, 1, 1, tzinfo=timezone.utc)
        value = datetime(2025, 6, 15, tzinfo=timezone.utc)
        assert self._validate_not_before(value, min_date) is True

    def test_not_after(self):
        max_date = datetime(2025, 12, 31, tzinfo=timezone.utc)
        value = datetime(2025, 6, 15, tzinfo=timezone.utc)
        assert self._validate_not_after(value, max_date) is True

    def test_one_of(self):
        assert self._validate_one_of("active", ["active", "inactive"]) is True
        assert self._validate_one_of("unknown", ["active", "inactive"]) is False

    def test_none_of(self):
        assert self._validate_none_of("valid", ["invalid", "banned"]) is True
        assert self._validate_none_of("banned", ["invalid", "banned"]) is False


# --- Composite validation patterns ---


class TestCompositeValidation:
    """Tests for composite validation patterns."""

    def _validate_all(self, value, validators):
        """All validators must pass."""
        for validator in validators:
            if not validator(value):
                return False
        return True

    def _validate_any(self, value, validators):
        """At least one validator must pass."""
        for validator in validators:
            if validator(value):
                return True
        return False

    def _validate_with_message(self, value, validators):
        """Run validators and collect error messages."""
        errors = []
        for name, validator in validators.items():
            if not validator(value):
                errors.append(name)
        return len(errors) == 0, errors

    def test_all_pass(self):
        validators = [lambda x: x > 0, lambda x: x < 100]
        assert self._validate_all(50, validators) is True

    def test_all_fail(self):
        validators = [lambda x: x > 0, lambda x: x < 100]
        assert self._validate_all(150, validators) is False

    def test_any_pass(self):
        validators = [lambda x: x == "a", lambda x: x == "b"]
        assert self._validate_any("b", validators) is True

    def test_any_fail(self):
        validators = [lambda x: x == "a", lambda x: x == "b"]
        assert self._validate_any("c", validators) is False

    def test_with_messages(self):
        validators = {
            "positive": lambda x: x > 0,
            "under_100": lambda x: x < 100,
        }
        valid, errors = self._validate_with_message(150, validators)
        assert valid is False
        assert "under_100" in errors
