"""Actual local graph boundaries precede any mocked Xcode command."""
import json
from pathlib import Path
import plistlib
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch

import auto_detect
import native_dependency_guard as guard


def project(root, name="App.xcodeproj", extra=None):
    path = root / name
    path.mkdir(parents=True, exist_ok=True)
    rows = {"root": {"isa": "PBXProject", "mainGroup": "group"},
            "group": {"isa": "PBXGroup", "children": [], "sourceTree": "<group>"}}
    rows.update(extra or {})
    (path / "project.pbxproj").write_bytes(plistlib.dumps({"objects": rows}))
    return path


class GuardTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory(prefix="app-robot-native-guard-")
        self.addCleanup(self.temp.cleanup)
        self.root = Path(self.temp.name).resolve()
        auto_detect._clear_caches()

    def test_selected_local_source_ignores_unselected_remote_sample(self):
        project(self.root)
        project(self.root, "sample/Remote.xcodeproj", {"bad": {"isa": "XCRemoteSwiftPackageReference"}})
        self.assertEqual(guard.check(self.root, "App.xcodeproj")["projects"], 1)

    def test_nested_group_dependency_is_checked(self):
        rows = {"group": {"isa": "PBXGroup", "children": ["nested"]},
                "nested": {"isa": "PBXGroup", "path": "vendor", "children": ["ref"]},
                "ref": {"isa": "PBXFileReference", "path": "Child.xcodeproj", "sourceTree": "<group>"}}
        project(self.root, extra=rows)
        project(self.root, "vendor/Child.xcodeproj")
        self.assertEqual(guard.check(self.root, "App.xcodeproj")["projects"], 2)
        project(self.root, "vendor/Child.xcodeproj", {"bad": {"isa": "XCLocalSwiftPackageReference"}})
        with self.assertRaisesRegex(guard.Refused, "native_package_proxy_transport_required"):
            guard.check(self.root, "App.xcodeproj")

    def test_workspace_pods_deferral_is_exact_and_preinstallation_only(self):
        project(self.root)
        workspace = self.root / "App.xcworkspace"
        workspace.mkdir()
        xml = ('<Workspace><FileRef location="group:App.xcodeproj"/>'
               '<FileRef location="group:Pods/Pods.xcodeproj"/></Workspace>')
        (workspace / "contents.xcworkspacedata").write_text(xml)
        (self.root / "Podfile").write_text("fixture")
        with self.assertRaises(OSError):
            guard.check(self.root, "App.xcworkspace", before_pods=True)
        self.assertEqual(guard.check(self.root, "App.xcworkspace", before_pods=True,
                                     initial_pods_lock=True)["projects"], 1)
        with self.assertRaisesRegex(guard.Refused, "native_initial_lock_phase_required"):
            guard.check(self.root, "App.xcworkspace", initial_pods_lock=True)
        (self.root / "Podfile.lock").write_text("fixture")
        self.assertEqual(guard.check(self.root, "App.xcworkspace", before_pods=True)["phase"], "before_pods")
        with self.assertRaises(OSError):
            guard.check(self.root, "App.xcworkspace")
        project(self.root, "Pods/Pods.xcodeproj")
        self.assertEqual(guard.check(self.root, "App.xcworkspace")["projects"], 2)

    def test_scheme_cannot_import_an_unexamined_project(self):
        path = project(self.root)
        project(self.root, "Other.xcodeproj", {"bad": {"isa": "XCRemoteSwiftPackageReference"}})
        schemes = path / "xcshareddata/xcschemes"
        schemes.mkdir(parents=True)
        (schemes / "App.xcscheme").write_text(
            '<Scheme><BuildableReference ReferencedContainer="container:Other.xcodeproj"/></Scheme>')
        with self.assertRaisesRegex(guard.Refused, "native_package_proxy_transport_required"):
            guard.check(self.root, "App.xcodeproj")

    def test_outside_symlink_and_ambiguous_selection_refuse(self):
        project(self.root)
        project(self.root, "Other.xcodeproj")
        with self.assertRaisesRegex(guard.Refused, "native_project_selection_required"):
            guard.check(self.root)
        (self.root / "Outside.xcodeproj").symlink_to(self.root.parent)
        with self.assertRaisesRegex(guard.Refused, "native_workspace_reference_unverified"):
            guard.check(self.root, "Outside.xcodeproj")

    def test_remote_graph_refuses_each_actual_xcode_entry_before_subprocess(self):
        project(self.root, extra={"bad": {"isa": "XCRemoteSwiftPackageReference",
                                          "repositoryURL": "https://secret.invalid"}})
        calls = [lambda: auto_detect._list_schemes(self.root, "App.xcodeproj", ""),
                 lambda: auto_detect._show_build_settings(self.root, "App.xcodeproj", "", "App", "Debug"),
                 lambda: auto_detect._resolve_package_dependencies(self.root, "App.xcodeproj", "")]
        with patch.object(auto_detect.subprocess, "run") as run:
            for call in calls:
                with self.assertRaisesRegex(guard.Refused, "native_package_proxy_transport_required"):
                    call()
        run.assert_not_called()
        result = subprocess.run([sys.executable, guard.__file__, str(self.root), "--container", "App.xcodeproj"],
                                capture_output=True, text=True)
        self.assertEqual(result.returncode, 1)
        self.assertNotIn("secret.invalid", result.stdout + result.stderr)
        self.assertIn("CocoaPods", json.loads(result.stdout)["next"])

    def test_workspace_package_folder_is_not_treated_as_an_ordinary_document(self):
        project(self.root)
        package = self.root / "LocalPackage"
        package.mkdir()
        (package / "Package.swift").write_text("// owns a potentially remote SwiftPM graph")
        workspace = self.root / "App.xcworkspace"
        workspace.mkdir()
        (workspace / "contents.xcworkspacedata").write_text(
            '<Workspace><FileRef location="group:App.xcodeproj"/>'
            '<FileRef location="group:LocalPackage"/></Workspace>')
        with self.assertRaisesRegex(guard.Refused, "native_package_proxy_transport_required"):
            guard.check(self.root, "App.xcworkspace")
