#!/usr/bin/env python3

import importlib.util
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

SCRIPT = Path(__file__).with_name("check_apple_spm_locks.py")
SPEC = importlib.util.spec_from_file_location("check_apple_spm_locks", SCRIPT)
CHECKER = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
SPEC.loader.exec_module(CHECKER)

GOOD_VERSION = "8.58.3"
GOOD_REVISION = "dad229c665bfd043c5d80ac7aa77717cbd19a1c3"


class AppleSpmLockCheckerTest(unittest.TestCase):
    def setUp(self) -> None:
        self.temporary = tempfile.TemporaryDirectory()
        self.root = Path(self.temporary.name)
        self._write_package_config()
        self._write_manifests(GOOD_VERSION)
        self._write_all_locks(GOOD_VERSION, GOOD_REVISION)

    def tearDown(self) -> None:
        self.temporary.cleanup()

    def _write_package_config(self, root_uri: str = "../packages/sentry_flutter") -> None:
        path = self.root / ".dart_tool/package_config.json"
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(
            json.dumps(
                {
                    "configVersion": 2,
                    "packages": [{"name": "sentry_flutter", "rootUri": root_uri}],
                }
            ),
            encoding="utf-8",
        )

    def _write_manifests(self, version: str) -> None:
        source = f'.package(url: "https://github.com/getsentry/sentry-cocoa", exact: "{version}")\n'
        for platform in ("ios", "macos"):
            path = self.root / f"packages/sentry_flutter/{platform}/sentry_flutter/Package.swift"
            path.parent.mkdir(parents=True)
            path.write_text(source, encoding="utf-8")

    def _lock_payload(self, version: str, revision: str) -> dict:
        return {
            "originHash": "fixture",
            "pins": [
                {
                    "identity": "sentry-cocoa",
                    "kind": "remoteSourceControl",
                    "location": "https://github.com/getsentry/sentry-cocoa",
                    "state": {"revision": revision, "version": version},
                }
            ],
            "version": 2,
        }

    def _write_lock(self, relative: Path, version: str, revision: str) -> None:
        path = self.root / relative
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(self._lock_payload(version, revision)), encoding="utf-8")

    def _write_all_locks(self, version: str, revision: str) -> None:
        for paths in CHECKER.LOCK_PAIRS.values():
            for path in paths:
                self._write_lock(path, version, revision)

    def test_consistent_graph_passes_library_and_cli(self) -> None:
        self.assertEqual([], CHECKER.validate(self.root))
        completed = subprocess.run(
            [sys.executable, str(SCRIPT), "--root", str(self.root)],
            check=False,
            capture_output=True,
            text=True,
        )
        self.assertEqual(0, completed.returncode, completed.stdout + completed.stderr)
        self.assertIn("Apple SwiftPM locks match", completed.stdout)

    def test_absolute_file_uri_resolves_sentry_manifests(self) -> None:
        self._write_package_config((self.root / "packages/sentry_flutter").resolve().as_uri())

        self.assertEqual([], CHECKER.validate(self.root))

    def test_reports_project_workspace_version_mismatch(self) -> None:
        workspace = CHECKER.LOCK_PAIRS["iOS"][1]
        self._write_lock(workspace, "8.58.0", "old")

        errors = CHECKER.validate(self.root)

        self.assertTrue(any(str(workspace) in error and "sentry-cocoa" in error for error in errors))
        self.assertTrue(any("iOS SwiftPM locks differ" in error for error in errors))

    def test_reports_all_locks_stale_against_manifests(self) -> None:
        self._write_all_locks("8.58.0", "old")

        errors = CHECKER.validate(self.root)

        self.assertEqual(4, sum("manifest requires exactly 8.58.3" in error for error in errors))

    def test_reports_same_version_with_different_revision(self) -> None:
        workspace = CHECKER.LOCK_PAIRS["macOS"][1]
        self._write_lock(workspace, GOOD_VERSION, "different")

        errors = CHECKER.validate(self.root)

        self.assertTrue(any("macOS SwiftPM locks differ" in error and "sentry-cocoa" in error for error in errors))
        self.assertTrue(any("state differs" in error for error in errors))

    def test_reports_missing_resolved_package_inputs(self) -> None:
        (self.root / ".dart_tool/package_config.json").unlink()

        errors = CHECKER.validate(self.root)

        self.assertTrue(any("flutter pub get first" in error for error in errors))


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