def test_mccode_pooch_tags():
    from mccode_antlr import Flavor
    from mccode_antlr.reader import default_registries
    for flavor in (Flavor.BASE, Flavor.MCSTAS, Flavor.MCXTRACE,):
        for reg in default_registries(flavor):
            assert reg.version != "main"


# ---------------------------------------------------------------------------
# _parse_gitref_spec — pure parsing, no network
# ---------------------------------------------------------------------------

class TestParseGitrefSpec:
    """Unit tests for the compact git-reference spec parser."""

    def _parse(self, spec):
        from mccode_antlr.reader.registry import _parse_gitref_spec
        return _parse_gitref_spec(spec)

    # -- git+ prefix ----------------------------------------------------------

    def test_git_plus_basic(self):
        result = self._parse('git+https://github.com/owner/repo@v1.0')
        assert result == ('repo', 'https://github.com/owner/repo', 'v1.0', 'pooch-registry.txt')

    def test_git_plus_strips_dot_git(self):
        result = self._parse('git+https://github.com/owner/repo.git@v2.3')
        assert result == ('repo', 'https://github.com/owner/repo', 'v2.3', 'pooch-registry.txt')

    def test_git_plus_custom_registry_file(self):
        result = self._parse('git+https://github.com/owner/repo@v1.0#my-registry.txt')
        assert result == ('repo', 'https://github.com/owner/repo', 'v1.0', 'my-registry.txt')

    def test_git_plus_commit_sha(self):
        result = self._parse('git+https://github.com/owner/repo@abc1234')
        assert result is not None
        name, url, version, reg = result
        assert version == 'abc1234'
        assert name == 'repo'

    def test_git_plus_no_at_returns_none(self):
        assert self._parse('git+https://github.com/owner/repo') is None

    def test_git_plus_strips_trailing_slash(self):
        result = self._parse('git+https://github.com/owner/repo/@v1.0')
        assert result is not None
        assert result[1] == 'https://github.com/owner/repo'

    # -- owner/repo@version ---------------------------------------------------

    def test_short_form_basic(self):
        result = self._parse('owner/repo@v3.5')
        assert result == ('repo', 'https://github.com/owner/repo', 'v3.5', 'pooch-registry.txt')

    def test_short_form_custom_registry_file(self):
        result = self._parse('owner/repo@v3.5#mcstas-registry.txt')
        assert result == ('repo', 'https://github.com/owner/repo', 'v3.5', 'mcstas-registry.txt')

    def test_short_form_tag_with_dots(self):
        result = self._parse('mccode-dev/McCode@v3.5.31')
        assert result is not None
        _, _, version, _ = result
        assert version == 'v3.5.31'

    # -- non-matching inputs --------------------------------------------------

    def test_space_separated_returns_none(self):
        assert self._parse('name https://example.com v1 reg.txt') is None

    def test_plain_path_returns_none(self):
        assert self._parse('/some/local/path') is None

    def test_empty_returns_none(self):
        assert self._parse('') is None

    def test_no_at_returns_none(self):
        assert self._parse('owner/repo') is None

    def test_multiple_slashes_before_at_returns_none(self):
        # Looks like a URL path fragment, not owner/repo — should not match
        assert self._parse('owner/repo/extra@v1') is None


# ---------------------------------------------------------------------------
# registry_from_specification — new formats (GitHubRegistry mocked)
# ---------------------------------------------------------------------------

class TestRegistryFromSpecificationNewFormats:
    """Tests that the new compact formats reach GitHubRegistry with correct args."""

    def _call(self, spec, monkeypatch):
        """Call registry_from_specification with GitHubRegistry.__init__ mocked."""
        captured = {}

        import mccode_antlr.reader.registry as reg_mod

        original_init = reg_mod.GitHubRegistry.__init__

        def fake_init(self, name, url, version, filename=None, **kw):
            captured.update(name=name, url=url, version=version, filename=filename)
            # Minimal init to avoid network calls
            self.name = name
            self.url = url
            self.version = version
            self.filename = filename
            self.pooch = None
            self._stashed_registry = None

        monkeypatch.setattr(reg_mod.GitHubRegistry, '__init__', fake_init)
        result = reg_mod.registry_from_specification(spec)
        return result, captured

    def test_git_plus_creates_github_registry(self, monkeypatch):
        reg, cap = self._call('git+https://github.com/owner/repo@v1.0', monkeypatch)
        assert cap['name'] == 'repo'
        assert cap['url'] == 'https://github.com/owner/repo'
        assert cap['version'] == 'v1.0'
        assert cap['filename'] == 'pooch-registry.txt'

    def test_short_form_creates_github_registry(self, monkeypatch):
        reg, cap = self._call('owner/repo@v3.5.31', monkeypatch)
        assert cap['name'] == 'repo'
        assert cap['url'] == 'https://github.com/owner/repo'
        assert cap['version'] == 'v3.5.31'
        assert cap['filename'] == 'pooch-registry.txt'

    def test_custom_registry_file_passed_through(self, monkeypatch):
        reg, cap = self._call('owner/repo@v1.0#custom.txt', monkeypatch)
        assert cap['filename'] == 'custom.txt'

    def test_git_plus_dot_git_stripped(self, monkeypatch):
        reg, cap = self._call('git+https://github.com/owner/repo.git@v2.0', monkeypatch)
        assert cap['url'] == 'https://github.com/owner/repo'
        assert cap['name'] == 'repo'



# ---------------------------------------------------------------------------
# Registry.to_file — serialization format
# ---------------------------------------------------------------------------

class TestRegistryToFile:
    """Tests for the Registry: <spec> comment format written by to_file."""

    def _to_file_str(self, registry):
        from io import StringIO
        from mccode_antlr.common import TextWrapper
        out = StringIO()
        registry.to_file(out, TextWrapper())
        return out.getvalue().strip()

    def test_remote_registry_format(self):
        import mccode_antlr.reader.registry as rm
        reg = rm.RemoteRegistry('mylib', 'https://example.com/repo', 'v1.0', 'mylib-registry.txt')
        line = self._to_file_str(reg)
        assert line == 'Registry: mylib https://example.com/repo v1.0 mylib-registry.txt'

    def test_local_registry_format(self, tmp_path):
        import mccode_antlr.reader.registry as rm
        reg = rm.LocalRegistry('mylib', str(tmp_path))
        line = self._to_file_str(reg)
        assert line == f'Registry: mylib {tmp_path.as_posix()}'

    def test_remote_registry_roundtrips(self):
        import mccode_antlr.reader.registry as rm
        reg = rm.RemoteRegistry('mylib', 'https://example.com/repo', 'v1.0', 'mylib-registry.txt')
        line = self._to_file_str(reg)
        spec = line[len('Registry:'):].strip()
        # format 4 → registry_from_specification would create a GitHubRegistry;
        # check the spec is parseable back to the right name/url/version/filename
        parts = spec.split()
        assert parts == ['mylib', 'https://example.com/repo', 'v1.0', 'mylib-registry.txt']

    def test_github_registry_with_separate_registry_url_includes_fifth_field(self, monkeypatch):
        """GitHubRegistry with a separate registry URL should emit 5 fields."""
        import mccode_antlr.reader.registry as rm
        calls = {}
        def fake_init(self, name, url, version, filename=None, registry=None, priority=0):
            self.name = name
            self.url = url
            self.version = version
            self.filename = filename or f'{name}-registry.txt'
            self._stashed_registry = registry if isinstance(registry, str) else None
            self.pooch = None
            self.priority = priority
            calls['registry'] = registry
        monkeypatch.setattr(rm.GitHubRegistry, '__init__', fake_init)
        reg = rm.GitHubRegistry('files', 'https://github.com/org/files-repo', 'v2.0',
                                registry='https://github.com/org/registry-repo')
        line = self._to_file_str(reg)
        parts = line[len('Registry:'):].strip().split()
        assert parts == [
            'files',
            'https://github.com/org/files-repo',
            'v2.0',
            'files-registry.txt',
            'https://github.com/org/registry-repo',
        ]

    def test_github_registry_separate_registry_url_roundtrips(self, monkeypatch):
        """Round-trip: to_file then registry_from_specification preserves the registry URL."""
        import mccode_antlr.reader.registry as rm
        captured = {}
        def fake_init(self, name, url, version, filename=None, registry=None, priority=0):
            self.name = name
            self.url = url
            self.version = version
            self.filename = filename or f'{name}-registry.txt'
            self._stashed_registry = registry if isinstance(registry, str) else None
            self.pooch = None
            self.priority = priority
            captured.update(name=name, url=url, version=version, filename=filename, registry=registry)
        monkeypatch.setattr(rm.GitHubRegistry, '__init__', fake_init)

        reg = rm.GitHubRegistry('files', 'https://github.com/org/files-repo', 'v2.0',
                                registry='https://github.com/org/registry-repo')
        line = self._to_file_str(reg)
        spec = line[len('Registry:'):].strip()

        # Reset captured so we can record what registry_from_specification passes
        captured.clear()
        recovered = rm.registry_from_specification(spec)
        assert recovered is not None
        assert captured['name'] == 'files'
        assert captured['url'] == 'https://github.com/org/files-repo'
        assert captured['version'] == 'v2.0'
        assert captured['registry'] == 'https://github.com/org/registry-repo'

    def test_local_registry_roundtrips(self, tmp_path):
        import mccode_antlr.reader.registry as rm
        reg = rm.LocalRegistry('mylib', str(tmp_path))
        line = self._to_file_str(reg)
        spec = line[len('Registry:'):].strip()
        recovered = rm.registry_from_specification(spec)
        assert recovered is not None
        assert recovered.name == 'mylib'
        assert recovered.root == tmp_path.resolve()


# ---------------------------------------------------------------------------
# registries_from_instr_header — header comment scanner
# ---------------------------------------------------------------------------

class TestRegistriesFromInstrHeader:

    def test_no_block_comment_returns_empty(self):
        from mccode_antlr.reader.registry import registries_from_instr_header
        assert registries_from_instr_header('DEFINE INSTRUMENT foo() TRACE END') == []

    def test_comment_without_registry_lines_returns_empty(self):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = '/* Instrument foo\nSource: foo.instr\n*/\nDEFINE INSTRUMENT foo()\n'
        assert registries_from_instr_header(source) == []

    def test_local_registry_recovered(self, tmp_path):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = f'/* Instrument foo\nRegistry: mylib {tmp_path.as_posix()}\n*/\n'
        regs = registries_from_instr_header(source)
        assert len(regs) == 1
        assert regs[0].name == 'mylib'
        assert regs[0].root == tmp_path.resolve()

    def test_nonexistent_local_path_skipped(self):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = '/* Instrument foo\nRegistry: mylib /nonexistent/path/that/does/not/exist\n*/\n'
        regs = registries_from_instr_header(source)
        assert regs == []

    def test_multiple_registries_recovered(self, tmp_path):
        from mccode_antlr.reader.registry import registries_from_instr_header
        dir_a = tmp_path / 'a'
        dir_b = tmp_path / 'b'
        dir_a.mkdir()
        dir_b.mkdir()
        source = (
            f'/* Instrument foo\n'
            f'Registry: libA {dir_a.as_posix()}\n'
            f'Registry: libB {dir_b.as_posix()}\n'
            f'*/\n'
        )
        regs = registries_from_instr_header(source)
        assert len(regs) == 2
        assert {r.name for r in regs} == {'libA', 'libB'}

    def test_duplicate_names_deduplicated(self, tmp_path):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = (
            f'/* Instrument foo\n'
            f'Registry: mylib {tmp_path.as_posix()}\n'
            f'Registry: mylib {tmp_path.as_posix()}\n'
            f'*/\n'
        )
        regs = registries_from_instr_header(source)
        assert len(regs) == 1

    def test_unclosed_comment_returns_empty(self):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = '/* Instrument foo\nRegistry: mylib /some/path\n'
        assert registries_from_instr_header(source) == []

    def test_leading_whitespace_ignored(self, tmp_path):
        from mccode_antlr.reader.registry import registries_from_instr_header
        source = f'\n\n/* Instrument foo\nRegistry: mylib {tmp_path.as_posix()}\n*/\n'
        regs = registries_from_instr_header(source)
        assert len(regs) == 1
