"""Exercise real child scoping and committed bundle failures without contacting a provider."""
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest

import native_bundle

class BundleTests(unittest.TestCase):
    def setUp(self):
        temporary = tempfile.TemporaryDirectory(prefix="native-bundle-test-")
        self.addCleanup(temporary.cleanup)
        self.root = Path(temporary.name).resolve()
        (self.root / "ios").mkdir()
        self.env = {**os.environ, "PATH": str(self.root) + os.pathsep + os.environ["PATH"]}
        self.env.pop("BUNDLE_PATH", None)
        subprocess.run(["git", "init", "-q"], cwd=self.root, check=True, capture_output=True)
        bundle = self.root / "bundle"
        bundle.write_text('#!' + sys.executable + '\nimport os,sys\nfrom pathlib import Path\n'
                          'if sys.argv[1] == "check": raise SystemExit(int(os.environ.get("COLD_BUNDLE", "0")))\n'
                          'if sys.argv[1] == "install":\n'
                          ' print("PRIVATE_SENTINEL")\n'
                          ' if os.environ.get("CHANGE_LOCK"): Path("Gemfile.lock").write_text("changed")\n'
                          ' raise SystemExit(int(os.environ.get("INSTALL_EXIT", "0")))\n'
                          'assert sys.argv[1] == "exec"\n'
                          'os.environ["FIXTURE_BUNDLE_ACTIVE"] = "yes"\n'
                          'os.execvp(sys.argv[2], sys.argv[2:])\n')
        bundle.chmod(0o700)

    def commit(self, *files):
        for name in files:
            path = self.root / name
            path.write_text("fixture " + name + "\n")
        for argv in (["git", "add", *files],
                     ["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid",
                      "commit", "-qm", "Freeze fixture inputs"]):
            subprocess.run(argv, cwd=self.root, check=True, capture_output=True)

    def run_cli(self, script=None):
        script = script or ('import json,os; from pathlib import Path; '
                            'Path("child.json").write_text(json.dumps({k:os.environ.get(k) for k in '
                            '["FIXTURE_BUNDLE_ACTIVE","BUNDLE_GEMFILE","BUNDLE_FROZEN"]}))')
        result = subprocess.run([sys.executable, native_bundle.__file__, "--root", str(self.root),
                                 "--", sys.executable, "-c", script],
                                cwd=self.root, env=self.env, capture_output=True, text=True, timeout=20)
        annotations = [line for line in result.stderr.splitlines()
                       if line.startswith("::error title=native_prepare_failed::")]
        if result.returncode:
            self.assertEqual(len(annotations), 1)
            self.assertEqual(json.loads(annotations[0].split("::", 2)[2])["phase"], "bundle")
        else:
            self.assertEqual(annotations, [])
        return result.returncode, json.loads(result.stdout)

    def test_unconfigured_app_runs_the_command_directly(self):
        code, value = self.run_cli()
        self.assertEqual((code, value["ruby_bundle"]), (0, "not_configured"))
        child = json.loads((self.root / "child.json").read_text())
        self.assertIsNone(child["FIXTURE_BUNDLE_ACTIVE"])

    def test_nearest_bundle_scopes_configuration_and_preserves_locks(self):
        self.commit("Gemfile", "Gemfile.lock", "ios/Gemfile", "ios/Gemfile.lock", "ios/Podfile", "ios/Podfile.lock")
        self.env["COLD_BUNDLE"] = "1"
        code, value = self.run_cli()
        self.assertEqual((code, value["ruby_bundle"]), (0, "locked"))
        child = json.loads((self.root / "child.json").read_text())
        self.assertEqual(child["FIXTURE_BUNDLE_ACTIVE"], "yes")
        self.assertEqual(child["BUNDLE_GEMFILE"], str(self.root / "ios/Gemfile"))
        self.assertEqual(child["BUNDLE_FROZEN"], "true")

    def test_uncommitted_or_modified_bundle_refuses_before_configuration(self):
        (self.root / "Gemfile").write_text("uncommitted")
        self.assertEqual(self.run_cli()[1]["code"], "native_gemfile_commit_required")
        self.commit("Gemfile")
        self.assertEqual(self.run_cli()[1]["code"], "native_gemfile_lock_required")
        self.commit("Gemfile.lock")
        (self.root / "Gemfile.lock").write_text("changed")
        self.assertEqual(self.run_cli()[1]["code"], "native_gemfile_lock_commit_required")
        self.assertFalse((self.root / "child.json").exists())

    def test_install_failure_and_mutation_do_not_launch_configuration(self):
        self.commit("Gemfile", "Gemfile.lock")
        self.env.update(COLD_BUNDLE="1", INSTALL_EXIT="7")
        self.assertEqual(self.run_cli()[1]["code"], "native_bundle_install_failed")
        self.env.update(INSTALL_EXIT="0", CHANGE_LOCK="1")
        self.assertEqual(self.run_cli()[1]["code"], "native_bundle_inputs_changed")
        self.assertFalse((self.root / "child.json").exists())

    def test_failed_configuration_and_generated_lock_drift_cannot_pass(self):
        self.commit("Gemfile", "Gemfile.lock", "ios/Podfile", "ios/Podfile.lock")
        code, value = self.run_cli('print("PRIVATE_SENTINEL"); raise SystemExit(7)')
        self.assertEqual((code, value["code"]), (1, "native_configuration_failed"))
        script = 'from pathlib import Path; Path("ios/Podfile.lock").write_text("new serializer")'
        self.assertEqual(self.run_cli(script)[1]["code"], "native_podfile_lock_changed")
