"""Chaos — Entity batch boundaries: empty, single, boundary, multi-source."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import httpx
import pytest
import respx

from kanoniv.cloud import reconcile

from .conftest import (
    API_KEY,
    BASE_URL,
    JOB_ID,
    make_completed_job_response,
    make_job_run_response,
    make_mock_source,
    make_mock_spec,
)


def _setup_routes(router: respx.Router) -> None:
    router.post("/v1/identity/specs").mock(
        return_value=httpx.Response(200, json={"valid": True}),
    )
    router.post(url__regex=r"/v1/ingest/webhook/.+").mock(
        return_value=httpx.Response(200, json={"ingested": 0}),
    )
    router.post("/v1/jobs/run").mock(
        return_value=httpx.Response(200, json=make_job_run_response()),
    )
    router.get(f"/v1/jobs/{JOB_ID}").mock(
        return_value=httpx.Response(200, json=make_completed_job_response()),
    )


def _count_ingest_calls(router: respx.Router) -> int:
    return sum(
        1 for call in router.calls
        if "/v1/ingest/webhook/" in str(call.request.url)
        and call.request.method == "POST"
    )


class TestBatchBoundaries:
    """Entity batching respects _BATCH_SIZE=500."""

    @patch("kanoniv.cloud.time.sleep")
    def test_zero_entities_no_ingest(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[])
        # Override to_entities to return empty list
        source.to_entities = MagicMock(return_value=[])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 0

    @patch("kanoniv.cloud.time.sleep")
    def test_one_entity_single_batch(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[{"id": "1"}])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 1

    @patch("kanoniv.cloud.time.sleep")
    def test_499_single_batch(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[{"id": str(i)} for i in range(499)])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 1

    @patch("kanoniv.cloud.time.sleep")
    def test_500_single_batch(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[{"id": str(i)} for i in range(500)])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 1

    @patch("kanoniv.cloud.time.sleep")
    def test_501_two_batches(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[{"id": str(i)} for i in range(501)])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 2

    @patch("kanoniv.cloud.time.sleep")
    def test_1500_three_batches(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        source = make_mock_source(name="crm", rows=[{"id": str(i)} for i in range(1500)])
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 3

    @patch("kanoniv.cloud.time.sleep")
    def test_multiple_sources_separate_batches(
        self, mock_sleep: MagicMock, mock_api: respx.Router,
    ):
        _setup_routes(mock_api)
        s1 = make_mock_source(name="crm", rows=[{"id": str(i)} for i in range(501)])
        s2 = make_mock_source(name="erp", rows=[{"id": str(i)} for i in range(501)])
        reconcile([s1, s2], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)
        assert _count_ingest_calls(mock_api) == 4  # 2 batches per source

    @patch("kanoniv.cloud.time.sleep")
    def test_batch_preserves_order(self, mock_sleep: MagicMock, mock_api: respx.Router):
        _setup_routes(mock_api)
        rows = [{"id": str(i), "name": f"entity-{i}"} for i in range(600)]
        source = make_mock_source(name="crm", rows=rows)
        reconcile([source], make_mock_spec(), api_key=API_KEY, base_url=BASE_URL)

        ingest_calls = [
            call for call in mock_api.calls
            if "/v1/ingest/webhook/" in str(call.request.url)
            and call.request.method == "POST"
        ]
        first_batch = json.loads(ingest_calls[0].request.read())
        assert len(first_batch) == 500
        assert first_batch[0]["data"]["id"] == "0"
        assert first_batch[499]["data"]["id"] == "499"
