from __future__ import annotations

import unittest
from collections.abc import Mapping, Sequence
from unittest.mock import patch

from govoplan_core.core.modules import ModuleManifest
from govoplan_core.core.registry import PlatformRegistry
from govoplan_core.tenancy.service import tenant_counts_many


EXPECTED_EMPTY_COUNTS = {
    "users": 0,
    "active_users": 0,
    "groups": 0,
    "campaigns": 0,
    "files": 0,
    "api_keys": 0,
    "active_api_keys": 0,
}


class _BatchAccessAdministration:
    def __init__(self) -> None:
        self.calls: list[tuple[str, ...]] = []

    def tenant_counts_many(
        self,
        session: object,
        tenant_ids: Sequence[str],
    ) -> Mapping[str, Mapping[str, int]]:
        del session
        ids = tuple(tenant_ids)
        self.calls.append(ids)
        return {
            tenant_id: {
                "users": index + 1,
                "active_users": index + 1,
                "groups": 1,
                "api_keys": 0,
                "active_api_keys": 0,
            }
            for index, tenant_id in enumerate(ids)
        }


class TenantSummaryBatchingTests(unittest.TestCase):
    def test_absent_providers_return_complete_zero_summaries(self) -> None:
        registry = PlatformRegistry()
        with (
            patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
            patch("govoplan_core.tenancy.service._access_administration", return_value=None),
        ):
            result = tenant_counts_many(object(), ["tenant-1", "tenant-2"])

        self.assertEqual(
            {
                "tenant-1": EXPECTED_EMPTY_COUNTS,
                "tenant-2": EXPECTED_EMPTY_COUNTS,
            },
            result,
        )

    def test_legacy_single_tenant_provider_remains_supported(self) -> None:
        calls: list[str] = []

        def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
            del session
            calls.append(tenant_id)
            return {"campaigns": 1}

        registry = PlatformRegistry()
        registry.register(
            ModuleManifest(
                id="campaigns",
                name="Campaigns",
                version="test",
                tenant_summary_providers=(single_provider,),
            )
        )
        with (
            patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
            patch("govoplan_core.tenancy.service._access_administration", return_value=None),
        ):
            result = tenant_counts_many(
                object(),
                ["tenant-1", "tenant-2"],
                module_ids=("campaigns",),
            )

        self.assertEqual(["tenant-1", "tenant-2"], calls)
        self.assertEqual(1, result["tenant-1"]["campaigns"])
        self.assertEqual(1, result["tenant-2"]["campaigns"])

    def test_batch_provider_handles_single_item_and_partial_results(self) -> None:
        calls: list[tuple[str, ...]] = []

        def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
            raise AssertionError(f"single provider should not run for {tenant_id}")

        def batch_provider(
            session: object,
            tenant_ids: Sequence[str],
        ) -> Mapping[str, Mapping[str, int]]:
            del session
            calls.append(tuple(tenant_ids))
            return {tenant_ids[0]: {"files": 7}}

        registry = PlatformRegistry()
        registry.register(
            ModuleManifest(
                id="files",
                name="Files",
                version="test",
                tenant_summary_providers=(single_provider,),
                tenant_summary_batch_providers=(batch_provider,),
            )
        )
        with (
            patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
            patch("govoplan_core.tenancy.service._access_administration", return_value=None),
        ):
            single = tenant_counts_many(object(), ["tenant-1"], module_ids=("files",))
            partial = tenant_counts_many(
                object(),
                ["tenant-1", "tenant-2"],
                module_ids=("files",),
            )

        self.assertEqual([("tenant-1",), ("tenant-1", "tenant-2")], calls)
        self.assertEqual(7, single["tenant-1"]["files"])
        self.assertEqual(7, partial["tenant-1"]["files"])
        self.assertEqual(0, partial["tenant-2"]["files"])

    def test_batch_provider_failures_remain_visible(self) -> None:
        def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
            return {}

        def failing_provider(
            session: object,
            tenant_ids: Sequence[str],
        ) -> Mapping[str, Mapping[str, int]]:
            raise RuntimeError("summary unavailable")

        registry = PlatformRegistry()
        registry.register(
            ModuleManifest(
                id="files",
                name="Files",
                version="test",
                tenant_summary_providers=(single_provider,),
                tenant_summary_batch_providers=(failing_provider,),
            )
        )
        with (
            patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
            patch("govoplan_core.tenancy.service._access_administration", return_value=None),
            self.assertRaisesRegex(RuntimeError, "summary unavailable"),
        ):
            tenant_counts_many(object(), ["tenant-1"], module_ids=("files",))

    def test_provider_calls_are_bounded_for_large_pages(self) -> None:
        provider_calls = {"campaigns": 0, "files": 0}

        def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
            raise AssertionError(f"single provider should not run for {tenant_id}")

        def batch_provider(module_id: str):
            def provide(
                session: object,
                tenant_ids: Sequence[str],
            ) -> Mapping[str, Mapping[str, int]]:
                del session
                provider_calls[module_id] += 1
                return {tenant_id: {} for tenant_id in tenant_ids}

            return provide

        registry = PlatformRegistry()
        for module_id in ("campaigns", "files"):
            registry.register(
                ModuleManifest(
                    id=module_id,
                    name=module_id.title(),
                    version="test",
                    tenant_summary_providers=(single_provider,),
                    tenant_summary_batch_providers=(batch_provider(module_id),),
                )
            )
        access = _BatchAccessAdministration()
        tenant_ids = [f"tenant-{index}" for index in range(100)]
        with (
            patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
            patch("govoplan_core.tenancy.service._access_administration", return_value=access),
        ):
            result = tenant_counts_many(
                object(),
                tenant_ids,
                module_ids=("campaigns", "files"),
            )

        self.assertEqual({"campaigns": 1, "files": 1}, provider_calls)
        self.assertEqual([tuple(tenant_ids)], access.calls)
        self.assertEqual(100, len(result))
        self.assertEqual(100, result["tenant-99"]["users"])

    def test_registry_replacement_carries_batch_providers(self) -> None:
        def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
            return {}

        def batch_provider(
            session: object,
            tenant_ids: Sequence[str],
        ) -> Mapping[str, Mapping[str, int]]:
            return {}

        registry = PlatformRegistry()
        registry.replace(
            [
                ModuleManifest(
                    id="files",
                    name="Files",
                    version="test",
                    tenant_summary_providers=(single_provider,),
                    tenant_summary_batch_providers=(batch_provider,),
                )
            ]
        )

        self.assertIs(
            batch_provider,
            registry.tenant_summary_batch_providers()["files"],
        )


if __name__ == "__main__":
    unittest.main()
