"""
Tests for e4s_cl/cf/compiler.py - vendor detection from ELF .comment sections.

The per-vendor check functions are pure string predicates, so they are tested
directly.  compiler_vendor() is tested through unittest.mock.patch so that
these tests are independent of any binary on the test host.  _get_comment()
error-handling paths are exercised with synthetic inputs that require no
specific compiler toolchain to be installed.
"""

import os
import tempfile
from pathlib import Path
from unittest.mock import patch

import tests
from e4s_cl.cf.compiler import (
    _gnu_check,
    _llvm_check,
    _intel_check,
    _amd_check,
    _pgi_check,
    _armclang_check,
    _fujitsu_check,
    CompilerVendor,
    compiler_vendor,
    _get_comment,
    available_compilers,
)


class CompilerCheckFunctionTests(tests.TestCase):
    """Unit tests for the pure per-vendor string-predicate functions."""

    def test_gnu_check_true(self):
        self.assertTrue(_gnu_check('GCC: (GNU) 11.3.0 20220519'))

    def test_gnu_check_false(self):
        self.assertFalse(_gnu_check('clang version 14.0.0'))

    def test_llvm_check_true(self):
        self.assertTrue(_llvm_check('clang version 14.0.0 (http://llvm.org/git/clang.git)'))

    def test_llvm_check_false(self):
        self.assertFalse(_llvm_check('GCC: (GNU) 11.3.0'))

    def test_amd_check_true(self):
        self.assertTrue(_amd_check('AMD clang version 15.0.0 (ROCm)'))

    def test_amd_check_false(self):
        self.assertFalse(_amd_check('GCC: (GNU) 11.3.0'))

    def test_intel_check_always_false(self):
        # Current implementation is a stub that always returns False
        self.assertFalse(_intel_check('Intel(R) C++ Compiler 2021.9.0'))
        self.assertFalse(_intel_check(''))

    def test_pgi_check_always_false(self):
        self.assertFalse(_pgi_check('PGI Workstation 21.7'))
        self.assertFalse(_pgi_check(''))

    def test_armclang_check_always_false(self):
        self.assertFalse(_armclang_check('Arm C Compiler armclang 21.1'))
        self.assertFalse(_armclang_check(''))

    def test_fujitsu_check_always_false(self):
        self.assertFalse(_fujitsu_check('Fujitsu Fortran Compiler 4.7.0'))
        self.assertFalse(_fujitsu_check(''))


class CompilerVendorTests(tests.TestCase):
    """Tests for compiler_vendor() using a patched _get_comment.

    No binary files are required; vendor detection logic is completely isolated.
    """

    def test_gnu_binary(self):
        with patch('e4s_cl.cf.compiler._get_comment',
                   return_value='GCC: (GNU) 11.3.0'):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.GNU)

    def test_llvm_binary(self):
        with patch('e4s_cl.cf.compiler._get_comment',
                   return_value='clang version 14.0.0'):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.LLVM)

    def test_amd_binary(self):
        # AMD comment contains 'AMD', 'clang', and 'GCC' - AMD must win (highest precedence)
        with patch('e4s_cl.cf.compiler._get_comment',
                   return_value='AMD clang version 15.0 (ROCm) - GCC 11.3'):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.AMD)

    def test_llvm_over_gnu_precedence(self):
        # clang binaries may embed a GCC version string; LLVM must have priority over GNU
        with patch('e4s_cl.cf.compiler._get_comment',
                   return_value='clang version 14.0 - GCC: (GNU) 11.0'):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.LLVM)

    def test_empty_comment_defaults_gnu(self):
        # When neither AMD, LLVM, nor any other vendor matches, fall through to GNU default
        with patch('e4s_cl.cf.compiler._get_comment', return_value=''):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.GNU)

    def test_unknown_comment_defaults_gnu(self):
        with patch('e4s_cl.cf.compiler._get_comment',
                   return_value='Some unknown toolchain 99.0'):
            result = compiler_vendor(Path('/fake/binary'))
        self.assertEqual(result, CompilerVendor.GNU)


class GetCommentErrorHandlingTests(tests.TestCase):
    """Tests for _get_comment() when the file path is invalid or not an ELF."""

    def test_nonexistent_file_returns_empty(self):
        result = _get_comment(Path('/nonexistent/no_such_binary_4e2s_cl'))
        self.assertEqual(result, '')

    def test_directory_path_returns_empty(self):
        result = _get_comment(Path('/tmp'))
        self.assertEqual(result, '')

    def test_non_elf_text_file_returns_empty(self):
        with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as fh:
            fh.write(b'This is plain text, not an ELF binary.\n')
            fname = fh.name
        try:
            result = _get_comment(Path(fname))
            self.assertEqual(result, '')
        finally:
            os.unlink(fname)


class AvailableCompilersTests(tests.TestCase):
    """available_compilers() must return a set without raising regardless of what
    is installed.  We do not assert specific vendors because the test environment
    may have any combination of toolchains installed."""

    def test_returns_set(self):
        result = available_compilers()
        self.assertIsInstance(result, (set, frozenset))

    def test_all_values_are_compiler_vendor(self):
        for vendor in available_compilers():
            self.assertIsInstance(vendor, CompilerVendor)
