"""A stale/misconfigured group profile cannot be installed or cached as usable."""
import json
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock

import app_groups
import profile_manager


class GroupProfileTest(unittest.TestCase):
    def test_cached_profile_requires_exact_group_values(self):
        required = {"group.example.shared"}
        cached = mock.MagicMock(uuid="UUID", name="CI-App", filename="UUID.mobileprovision")
        cached.expiration = datetime.now(timezone.utc) + timedelta(days=100)
        for granted, reusable in [([], False), (["group.example.other"], False),
                                  (["group.example.shared", "group.example.extra"], True)]:
            with self.subTest(granted=granted), tempfile.TemporaryDirectory() as directory:
                with mock.patch.object(profile_manager.creds_store, "find_reusable_profile", return_value=cached), \
                        mock.patch.object(profile_manager.creds_store, "profile_path") as path, \
                        mock.patch.object(profile_manager, "decode_profile_plist", return_value={
                            "Entitlements": {app_groups.ENTITLEMENT: granted},
                        }), \
                        mock.patch.object(
                            profile_manager, "install_profile", return_value=("UUID", "T", None)) as install:
                    path.return_value.read_bytes.return_value = b"profile"
                    result = profile_manager._try_reuse_cached(
                        "App", "CI-App", "C", Path(directory), {}, {app_groups.ENTITLEMENT}, required,
                    )
                self.assertEqual(result is not None, reusable)
                self.assertEqual(install.called, reusable)

    def test_fresh_profile_missing_group_is_rejected_before_install_or_cache(self):
        with tempfile.TemporaryDirectory() as directory:
            with mock.patch.object(profile_manager, "ensure_bundle_id", return_value="B"), \
                    mock.patch.object(profile_manager.capabilities, "reconcile", return_value=False), \
                    mock.patch.object(profile_manager, "delete_profile_by_name"), \
                    mock.patch.object(profile_manager, "create_profile", return_value=b"profile"), \
                    mock.patch.object(app_groups, "decode_profile_plist", return_value={"Entitlements": {}}), \
                    mock.patch.object(profile_manager, "install_profile") as install, \
                    mock.patch.object(profile_manager.creds_store, "write_cached_profile") as write:
                with self.assertRaises(SystemExit) as caught:
                    profile_manager._provision_bundle(
                        "test", "App", "C", Path(directory), {}, False,
                        {app_groups.ENTITLEMENT}, {"group.example.shared"},
                    )
            install.assert_not_called()
            write.assert_not_called()
            receipt = json.loads(str(caught.exception).split("::", 2)[2])
            self.assertEqual(receipt["bundle_id"], "App")
            self.assertEqual(receipt["missing_group_identifiers"], ["group.example.shared"])

    def test_fresh_matching_profile_is_accepted(self):
        with mock.patch.object(app_groups, "decode_profile_plist", return_value={
            "Entitlements": {app_groups.ENTITLEMENT: ["group.example.shared"]},
        }):
            app_groups.assert_profile(b"profile", {"group.example.shared"}, "App", Path("unused"))

    def test_each_bundle_is_checked_against_its_own_group_requirements(self):
        expires = datetime.now(timezone.utc) + timedelta(days=100)
        with tempfile.TemporaryDirectory() as directory:
            with mock.patch.object(profile_manager, "ensure_bundle_id", side_effect=["A", "B"]), \
                    mock.patch.object(profile_manager.capabilities, "reconcile", return_value=False), \
                    mock.patch.object(profile_manager, "delete_profile_by_name"), \
                    mock.patch.object(profile_manager, "create_profile", return_value=b"profile"), \
                    mock.patch.object(app_groups, "decode_profile_plist", return_value={
                        "Entitlements": {app_groups.ENTITLEMENT: ["group.example.parent"]},
                    }), \
                    mock.patch.object(profile_manager, "_assert_carplay_entitlements"), \
                    mock.patch.object(
                        profile_manager, "install_profile", return_value=("U", "T", expires)) as install, \
                    mock.patch.object(profile_manager.creds_store, "write_cached_profile"), \
                    mock.patch.object(profile_manager.creds_store, "write_profile_manifest") as manifest:
                with self.assertRaises(SystemExit) as caught:
                    profile_manager.provision_all_bundles(
                        "test", ["App", "Broadcast"], "C", creds_dir=Path(directory), cache_hit=False,
                        entitlements_by_bundle={bid: {app_groups.ENTITLEMENT} for bid in ["App", "Broadcast"]},
                        app_groups_by_bundle={
                            "App": {"group.example.parent"}, "Broadcast": {"group.example.broadcast"},
                        },
                    )
            receipt = json.loads(str(caught.exception).split("::", 2)[2])
            self.assertEqual(receipt["bundle_id"], "Broadcast")
            self.assertEqual(receipt["missing_group_identifiers"], ["group.example.broadcast"])
            self.assertEqual(install.call_count, 1)
            manifest.assert_not_called()

    def test_profile_decode_failure_is_private_and_fails_closed(self):
        with mock.patch.object(app_groups, "decode_profile_plist", side_effect=ValueError("private raw bytes")):
            with self.assertRaises(SystemExit) as caught:
                app_groups.assert_profile(b"profile", {"group.example.shared"}, "App", Path("unused"))
        self.assertNotIn("private raw bytes", str(caught.exception))

    def test_no_group_declaration_keeps_existing_profile_path(self):
        with mock.patch.object(app_groups, "decode_profile_plist") as decode:
            app_groups.assert_profile(b"profile", set(), "App", Path("unused"))
        decode.assert_not_called()


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