"""Tests for the crons client."""

from __future__ import annotations

import json
from datetime import datetime, timezone

import httpx
import pytest

from langgraph_sdk.client import (
    CronClient,
    HttpClient,
    SyncCronClient,
    SyncHttpClient,
)


def _cron_payload() -> dict[str, object]:
    """Return a mock cron response payload."""
    return {
        "run_id": "run_123",
        "thread_id": "thread_123",
        "assistant_id": "asst_123",
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-02T00:00:00Z",
        "status": "success",
        "metadata": {},
        "multitask_strategy": "reject",
    }


@pytest.mark.asyncio
async def test_async_create_for_thread():
    """Test that CronClient.create_for_thread works without end_time."""
    cron = _cron_payload()

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/threads/thread_123/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 0 * * *"
        assert body["assistant_id"] == "asst_123"
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.create_for_thread(
            thread_id="thread_123",
            assistant_id="asst_123",
            schedule="0 0 * * *",
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_create_for_thread_with_end_time():
    """Test that CronClient.create_for_thread includes end_time in the payload."""
    cron = _cron_payload()
    end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/threads/thread_123/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 0 * * *"
        assert body["assistant_id"] == "asst_123"
        assert body["end_time"] == "2025-12-31T23:59:59+00:00"

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.create_for_thread(
            thread_id="thread_123",
            assistant_id="asst_123",
            schedule="0 0 * * *",
            end_time=end_time,
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_create():
    """Test that CronClient.create works without end_time."""
    cron = _cron_payload()

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 12 * * *"
        assert body["assistant_id"] == "asst_456"
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.create(
            assistant_id="asst_456",
            schedule="0 12 * * *",
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_create_with_end_time():
    """Test that CronClient.create includes end_time in the payload."""
    cron = _cron_payload()
    end_time = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 12 * * *"
        assert body["assistant_id"] == "asst_456"
        assert body["end_time"] == "2025-06-15T12:00:00+00:00"

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.create(
            assistant_id="asst_456",
            schedule="0 12 * * *",
            end_time=end_time,
        )

    assert result == cron


def test_sync_create_for_thread():
    """Test that SyncCronClient.create_for_thread works without end_time."""
    cron = _cron_payload()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/threads/thread_123/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 0 * * *"
        assert body["assistant_id"] == "asst_123"
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.create_for_thread(
            thread_id="thread_123",
            assistant_id="asst_123",
            schedule="0 0 * * *",
        )

    assert result == cron


def test_sync_create_for_thread_with_end_time():
    """Test that SyncCronClient.create_for_thread includes end_time in the payload."""
    cron = _cron_payload()
    end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/threads/thread_123/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 0 * * *"
        assert body["assistant_id"] == "asst_123"
        assert body["end_time"] == "2025-12-31T23:59:59+00:00"

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.create_for_thread(
            thread_id="thread_123",
            assistant_id="asst_123",
            schedule="0 0 * * *",
            end_time=end_time,
        )

    assert result == cron


def test_sync_create():
    """Test that SyncCronClient.create works without end_time."""
    cron = _cron_payload()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 12 * * *"
        assert body["assistant_id"] == "asst_456"
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.create(
            assistant_id="asst_456",
            schedule="0 12 * * *",
        )

    assert result == cron


def test_sync_create_with_end_time():
    """Test that SyncCronClient.create includes end_time in the payload."""
    cron = _cron_payload()
    end_time = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 12 * * *"
        assert body["assistant_id"] == "asst_456"
        assert body["end_time"] == "2025-06-15T12:00:00+00:00"

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.create(
            assistant_id="asst_456",
            schedule="0 12 * * *",
            end_time=end_time,
        )

    assert result == cron


@pytest.mark.parametrize(
    "enabled_value",
    [True, False],
    ids=["enabled", "disabled"],
)
def test_sync_create_with_enabled_parameter(enabled_value):
    """Test that SyncCronClient.create includes enabled parameter in the payload."""
    cron = _cron_payload()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons"

        body = json.loads(request.content)
        assert body["schedule"] == "0 12 * * *"
        assert body["assistant_id"] == "asst_456"
        assert body["enabled"] == enabled_value

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.create(
            assistant_id="asst_456",
            schedule="0 12 * * *",
            enabled=enabled_value,
        )

    assert result == cron


def _cron_response() -> dict[str, object]:
    """Return a mock Cron object response."""
    return {
        "cron_id": "cron_123",
        "assistant_id": "asst_123",
        "thread_id": "thread_123",
        "on_run_completed": None,
        "end_time": "2025-12-31T23:59:59+00:00",
        "schedule": "0 10 * * *",
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-02T00:00:00Z",
        "payload": {},
        "user_id": None,
        "next_run_date": "2024-01-03T10:00:00Z",
        "metadata": {},
        "enabled": True,
    }


@pytest.mark.asyncio
async def test_async_update():
    """Test that CronClient.update works with schedule and enabled parameters."""
    cron = _cron_response()

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 10 * * *"
        assert body["enabled"] is False
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.update(
            cron_id="cron_123",
            schedule="0 10 * * *",
            enabled=False,
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_update_with_end_time():
    """Test that CronClient.update includes end_time in the payload."""
    cron = _cron_response()
    end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 10 * * *"
        assert body["end_time"] == "2025-12-31T23:59:59+00:00"
        assert body["enabled"] is True

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.update(
            cron_id="cron_123",
            schedule="0 10 * * *",
            end_time=end_time,
            enabled=True,
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_update_clears_end_time():
    """Test that CronClient.update sends an explicit null end_time to clear it."""
    cron = _cron_response()

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        body = json.loads(request.content)
        assert "end_time" in body  # Explicit None must survive the None-strip
        assert body["end_time"] is None

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.update(cron_id="cron_123", end_time=None)

    assert result == cron


def test_sync_update():
    """Test that SyncCronClient.update works with schedule and enabled parameters."""
    cron = _cron_response()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 10 * * *"
        assert body["enabled"] is False
        assert "end_time" not in body  # Should be filtered out by the None check

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.update(
            cron_id="cron_123",
            schedule="0 10 * * *",
            enabled=False,
        )

    assert result == cron


def test_sync_update_with_end_time():
    """Test that SyncCronClient.update includes end_time in the payload."""
    cron = _cron_response()
    end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        # Parse the request body
        body = json.loads(request.content)
        assert body["schedule"] == "0 10 * * *"
        assert body["end_time"] == "2025-12-31T23:59:59+00:00"
        assert body["enabled"] is True

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.update(
            cron_id="cron_123",
            schedule="0 10 * * *",
            end_time=end_time,
            enabled=True,
        )

    assert result == cron


def test_sync_update_clears_end_time():
    """Test that SyncCronClient.update sends an explicit null end_time to clear it."""
    cron = _cron_response()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_123"

        body = json.loads(request.content)
        assert "end_time" in body  # Explicit None must survive the None-strip
        assert body["end_time"] is None

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.update(cron_id="cron_123", end_time=None)

    assert result == cron


@pytest.mark.parametrize(
    "enabled_value",
    [True, False],
    ids=["enabled", "disabled"],
)
def test_sync_update_with_enabled_parameter(enabled_value):
    """Test that SyncCronClient.update includes enabled parameter in the payload."""
    cron = _cron_response()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "PATCH"
        assert request.url.path == "/runs/crons/cron_456"

        body = json.loads(request.content)
        assert body["enabled"] == enabled_value
        assert "schedule" not in body  # Only enabled is set

        return httpx.Response(200, json=cron)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.update(
            cron_id="cron_456",
            enabled=enabled_value,
        )

    assert result == cron


@pytest.mark.asyncio
async def test_async_search_with_metadata():
    """Test that CronClient.search forwards metadata in the request body."""
    cron = _cron_response()

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons/search"

        body = json.loads(request.content)
        assert body["metadata"] == {"owner": "alice"}
        assert body["limit"] == 10
        assert body["offset"] == 0

        return httpx.Response(200, json=[cron])

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.search(metadata={"owner": "alice"})

    assert result == [cron]


@pytest.mark.asyncio
async def test_async_search_omits_empty_metadata():
    """Test that CronClient.search does not send metadata when not provided."""
    cron = _cron_response()

    async def handler(request: httpx.Request) -> httpx.Response:
        body = json.loads(request.content)
        assert "metadata" not in body
        return httpx.Response(200, json=[cron])

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        await cron_client.search()


@pytest.mark.asyncio
async def test_async_count_with_metadata():
    """Test that CronClient.count forwards metadata in the request body."""

    async def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons/count"

        body = json.loads(request.content)
        assert body["metadata"] == {"team": "infra"}

        return httpx.Response(200, json=2)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        result = await cron_client.count(metadata={"team": "infra"})

    assert result == 2


@pytest.mark.asyncio
async def test_async_count_omits_empty_metadata():
    """Test that CronClient.count does not send metadata when not provided."""

    async def handler(request: httpx.Request) -> httpx.Response:
        body = json.loads(request.content)
        assert "metadata" not in body
        return httpx.Response(200, json=0)

    transport = httpx.MockTransport(handler)
    async with httpx.AsyncClient(
        transport=transport, base_url="https://example.com"
    ) as client:
        http_client = HttpClient(client)
        cron_client = CronClient(http_client)
        await cron_client.count()


def test_sync_search_with_metadata():
    """Test that SyncCronClient.search forwards metadata in the request body."""
    cron = _cron_response()

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons/search"

        body = json.loads(request.content)
        assert body["metadata"] == {"owner": "alice"}

        return httpx.Response(200, json=[cron])

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.search(metadata={"owner": "alice"})

    assert result == [cron]


def test_sync_search_omits_empty_metadata():
    """Test that SyncCronClient.search does not send metadata when not provided."""
    cron = _cron_response()

    def handler(request: httpx.Request) -> httpx.Response:
        body = json.loads(request.content)
        assert "metadata" not in body
        return httpx.Response(200, json=[cron])

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        cron_client.search()


def test_sync_count_with_metadata():
    """Test that SyncCronClient.count forwards metadata in the request body."""

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/runs/crons/count"

        body = json.loads(request.content)
        assert body["metadata"] == {"team": "infra"}

        return httpx.Response(200, json=2)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        result = cron_client.count(metadata={"team": "infra"})

    assert result == 2


def test_sync_count_omits_empty_metadata():
    """Test that SyncCronClient.count does not send metadata when not provided."""

    def handler(request: httpx.Request) -> httpx.Response:
        body = json.loads(request.content)
        assert "metadata" not in body
        return httpx.Response(200, json=0)

    transport = httpx.MockTransport(handler)
    with httpx.Client(transport=transport, base_url="https://example.com") as client:
        http_client = SyncHttpClient(client)
        cron_client = SyncCronClient(http_client)
        cron_client.count()
