"""Unit tests for shared boundary validators."""

from datetime import datetime, timezone

import pytest

from nextcloud_mcp_server.utils.validation import (
    is_valid_nextcloud_doc_id,
    parse_modified_timestamp,
)


@pytest.mark.unit
@pytest.mark.parametrize(
    "value",
    [
        "1",
        "42",
        "1234567890",
        "9999999999999999999",
    ],
)
def test_accepts_positive_ascii_integers(value):
    """Any positive ASCII integer (no leading zero) is a valid doc_id."""
    assert is_valid_nextcloud_doc_id(value) is True


@pytest.mark.unit
@pytest.mark.parametrize(
    "value,reason",
    [
        ("", "empty string"),
        ("0", "MySQL AUTO_INCREMENT starts at 1"),
        ("01", "leading zero"),
        ("00", "leading zeros"),
        ("-1", "negative"),
        ("+1", "explicit sign"),
        ("1.0", "float-like"),
        (" 1", "leading whitespace"),
        ("1 ", "trailing whitespace"),
        ("1\n", "trailing newline"),
        ("abc", "alphabetic"),
        ("1a", "trailing letter"),
        ("a1", "leading letter"),
        # Unicode digit classes that pass str.isdigit() but are not ASCII.
        # `²` (U+00B2) is a superscript and would slip past the old guard.
        ("²", "Unicode superscript-2"),
        # `٢` (U+0662) Arabic-Indic digit two — passes both isdigit() and
        # isdecimal(), so only an explicit ASCII regex catches it.
        ("٢", "Arabic-Indic digit two"),
        # `१` (U+0967) Devanagari digit one — same story.
        ("१", "Devanagari digit one"),
        # Mixed ASCII + Unicode digits.
        ("1٢", "mixed ASCII + Arabic-Indic"),
    ],
)
def test_rejects_invalid_doc_ids(value, reason):
    """Reject empty/zero/leading-zero/non-ASCII/non-digit inputs."""
    assert is_valid_nextcloud_doc_id(value) is False, f"should reject: {reason}"


# parse_modified_timestamp (ADR-027) ---------------------------------------

_JAN_2026_UTC = int(datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp())


@pytest.mark.unit
@pytest.mark.parametrize(
    "value,expected",
    [
        (None, None),
        ("", None),
        ("   ", None),
        # RFC 3339 / ISO 8601
        ("2026-01-01T00:00:00Z", _JAN_2026_UTC),
        ("2026-01-01T00:00:00+00:00", _JAN_2026_UTC),
        # +02:00 offset is two hours earlier in UTC
        ("2026-01-01T02:00:00+02:00", _JAN_2026_UTC),
        # Naive datetime is assumed UTC
        ("2026-01-01T00:00:00", _JAN_2026_UTC),
        # Date-only ISO form
        ("2026-01-01", _JAN_2026_UTC),
        # Bare Unix seconds (string and int) pass through
        (str(_JAN_2026_UTC), _JAN_2026_UTC),
        (_JAN_2026_UTC, _JAN_2026_UTC),
        (0, 0),
        (1767225600.9, 1767225600),  # float truncates
    ],
)
def test_parse_modified_timestamp_accepts(value, expected):
    """RFC 3339 strings, Unix seconds, and None normalize to int seconds (UTC)."""
    assert parse_modified_timestamp(value) == expected


@pytest.mark.unit
@pytest.mark.parametrize(
    "value,reason",
    [
        ("not-a-date", "unparseable string"),
        ("2026-13-01T00:00:00Z", "invalid month"),
        (-1, "negative int"),
        (-5.0, "negative float"),
        (True, "bool is not a timestamp"),
        (False, "bool is not a timestamp"),
        ([], "wrong type"),
    ],
)
def test_parse_modified_timestamp_rejects(value, reason):
    """Bad formats / negatives / bools raise ValueError (→ McpError or HTTP 400)."""
    with pytest.raises(ValueError):
        parse_modified_timestamp(value)


@pytest.mark.unit
def test_parse_modified_timestamp_error_names_param():
    """The param_name is surfaced in the error for caller-friendly messages."""
    with pytest.raises(ValueError, match="modified_after"):
        parse_modified_timestamp("nope", param_name="modified_after")
