"""Tests for Mocha test runner functionality."""

import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

from junitparser import JUnitXml


class TestMochaJsonToJunitXml:
    """Tests for converting Mocha JSON reporter output to JUnit XML."""

    def test_passing_tests(self):
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 2, "passes": 2, "failures": 0, "duration": 50},
                "tests": [
                    {"title": "should add numbers", "fullTitle": "math should add numbers", "duration": 20, "err": {}},
                    {
                        "title": "should subtract numbers",
                        "fullTitle": "math should subtract numbers",
                        "duration": 30,
                        "err": {},
                    },
                ],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file)

            assert output_file.exists()
            xml = JUnitXml.fromfile(str(output_file))
            total_tests = sum(suite.tests for suite in xml)
            assert total_tests == 2

    def test_failing_tests(self):
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 1, "passes": 0, "failures": 1, "duration": 10},
                "tests": [
                    {
                        "title": "should fail",
                        "fullTitle": "errors should fail",
                        "duration": 10,
                        "err": {
                            "message": "expected 1 to equal 2",
                            "stack": "AssertionError: expected 1 to equal 2\n    at Context.<anonymous>",
                        },
                    }
                ],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file)

            assert output_file.exists()
            xml = JUnitXml.fromfile(str(output_file))
            total_failures = sum(suite.failures for suite in xml)
            assert total_failures == 1

    def test_pending_tests(self):
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 1, "passes": 0, "failures": 0, "pending": 1, "duration": 0},
                "tests": [
                    {
                        "title": "should be pending",
                        "fullTitle": "todo should be pending",
                        "duration": 0,
                        "pending": True,
                        "err": {},
                    }
                ],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file)

            assert output_file.exists()
            xml = JUnitXml.fromfile(str(output_file))
            # Should parse without error and have the test
            total_tests = sum(suite.tests for suite in xml)
            assert total_tests == 1

    def test_invalid_json_writes_empty_xml(self):
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml("not valid json {{{", output_file)

            assert output_file.exists()
            content = output_file.read_text()
            assert "<testsuites" in content

    def test_multiple_suites(self):
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 3, "passes": 3, "failures": 0, "duration": 100},
                "tests": [
                    {"title": "test1", "fullTitle": "suite A test1", "duration": 10, "err": {}},
                    {"title": "test2", "fullTitle": "suite A test2", "duration": 20, "err": {}},
                    {"title": "test3", "fullTitle": "suite B test3", "duration": 30, "err": {}},
                ],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file)

            xml = JUnitXml.fromfile(str(output_file))
            suite_names = [suite.name for suite in xml]
            assert "suite A" in suite_names
            assert "suite B" in suite_names

    def test_file_attribute_set_from_test_files(self):
        """When test_files are passed, the file attribute should be set on testcase elements."""
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 2, "passes": 2, "failures": 0, "duration": 50},
                "tests": [
                    {"title": "test1", "fullTitle": "escapeHtml test1", "duration": 10, "err": {}},
                    {"title": "test2", "fullTitle": "escapeHtml test2", "duration": 20, "err": {}},
                ],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            # Create a test file whose describe block matches the suite name
            test_file = tmpdir_path / "test_escapeHtml__unit_test_0.test.js"
            test_file.write_text(
                "const assert = require('node:assert/strict');\n"
                "const escapeHtml = require('../index.js');\n"
                "describe('escapeHtml', () => {\n"
                "  it('test1', () => { assert.ok(true); });\n"
                "  it('test2', () => { assert.ok(true); });\n"
                "});\n",
                encoding="utf-8",
            )

            output_file = tmpdir_path / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file, test_files=[test_file])

            # Parse the XML and verify file attributes
            import xml.etree.ElementTree as ET

            tree = ET.parse(output_file)
            root = tree.getroot()
            testcases = root.findall(".//testcase")
            assert len(testcases) == 2
            for tc in testcases:
                assert tc.get("file") == str(test_file)

    def test_file_attribute_uses_default_when_no_describe_match(self):
        """When describe name doesn't match, the default (first) test file should be used."""
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 10},
                "tests": [{"title": "test1", "fullTitle": "someOtherSuite test1", "duration": 10, "err": {}}],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            test_file = tmpdir_path / "test.test.js"
            test_file.write_text("// no describe block", encoding="utf-8")

            output_file = tmpdir_path / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file, test_files=[test_file])

            import xml.etree.ElementTree as ET

            tree = ET.parse(output_file)
            testcases = tree.getroot().findall(".//testcase")
            assert len(testcases) == 1
            assert testcases[0].get("file") == str(test_file)

    def test_no_file_attribute_when_no_test_files(self):
        """When test_files is not passed, no file attribute should be set."""
        from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml

        mocha_json = json.dumps(
            {
                "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 10},
                "tests": [{"title": "test1", "fullTitle": "suite test1", "duration": 10, "err": {}}],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = Path(tmpdir) / "results.xml"
            mocha_json_to_junit_xml(mocha_json, output_file)

            import xml.etree.ElementTree as ET

            tree = ET.parse(output_file)
            testcases = tree.getroot().findall(".//testcase")
            assert len(testcases) == 1
            assert testcases[0].get("file") is None


class TestExtractMochaJson:
    """Tests for extracting Mocha JSON from mixed stdout."""

    def test_clean_json(self):
        from codeflash.languages.javascript.mocha_runner import _extract_mocha_json

        data = {"stats": {"tests": 1}, "tests": []}
        result = _extract_mocha_json(json.dumps(data))
        assert result is not None
        assert json.loads(result)["stats"]["tests"] == 1

    def test_json_with_leading_output(self):
        from codeflash.languages.javascript.mocha_runner import _extract_mocha_json

        stdout = 'Some console output\n{"stats": {"tests": 1}, "tests": []}'
        result = _extract_mocha_json(stdout)
        assert result is not None
        assert json.loads(result)["stats"]["tests"] == 1

    def test_json_with_codeflash_markers(self):
        from codeflash.languages.javascript.mocha_runner import _extract_mocha_json

        data = {"stats": {"tests": 1}, "tests": []}
        stdout = f"!######START:test:module:0:test_name######!\n{json.dumps(data)}\n!######END######!"
        result = _extract_mocha_json(stdout)
        assert result is not None

    def test_no_json_returns_none(self):
        from codeflash.languages.javascript.mocha_runner import _extract_mocha_json

        result = _extract_mocha_json("no json here at all")
        assert result is None


class TestFindMochaProjectRoot:
    """Tests for finding Mocha project root."""

    def test_finds_mocharc_yml(self):
        from codeflash.languages.javascript.mocha_runner import _find_mocha_project_root

        with tempfile.TemporaryDirectory() as tmpdir:
            root = Path(tmpdir)
            (root / ".mocharc.yml").write_text("timeout: 5000\n")
            sub = root / "src" / "lib"
            sub.mkdir(parents=True)
            test_file = sub / "test.js"
            test_file.write_text("// test")

            result = _find_mocha_project_root(test_file)
            assert result == root

    def test_finds_mocharc_json(self):
        from codeflash.languages.javascript.mocha_runner import _find_mocha_project_root

        with tempfile.TemporaryDirectory() as tmpdir:
            root = Path(tmpdir)
            (root / ".mocharc.json").write_text("{}")
            test_file = root / "test.js"
            test_file.write_text("// test")

            result = _find_mocha_project_root(test_file)
            assert result == root

    def test_falls_back_to_package_json(self):
        from codeflash.languages.javascript.mocha_runner import _find_mocha_project_root

        with tempfile.TemporaryDirectory() as tmpdir:
            root = Path(tmpdir)
            (root / "package.json").write_text('{"name": "test"}')
            sub = root / "test"
            sub.mkdir()
            test_file = sub / "test.js"
            test_file.write_text("// test")

            result = _find_mocha_project_root(test_file)
            assert result == root


class TestMochaBehavioralCommand:
    """Tests for building Mocha behavioral commands."""

    def test_basic_command(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_behavioral_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_behavioral_command(test_files=[test_file])
            assert "npx" in cmd
            assert "mocha" in cmd
            assert "--reporter" in cmd
            assert "json" in cmd
            assert "--jobs" in cmd
            assert "1" in cmd
            assert "--exit" in cmd

    def test_timeout_flag(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_behavioral_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_behavioral_command(test_files=[test_file], timeout=30)
            timeout_idx = cmd.index("--timeout")
            assert cmd[timeout_idx + 1] == "30000"

    def test_default_timeout(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_behavioral_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_behavioral_command(test_files=[test_file])
            timeout_idx = cmd.index("--timeout")
            assert cmd[timeout_idx + 1] == "60000"


class TestMochaBenchmarkingCommand:
    """Tests for building Mocha benchmarking commands."""

    def test_basic_command(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_benchmarking_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_benchmarking_command(test_files=[test_file])
            assert "npx" in cmd
            assert "mocha" in cmd
            assert "--exit" in cmd

    def test_default_timeout_is_longer(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_benchmarking_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_benchmarking_command(test_files=[test_file])
            timeout_idx = cmd.index("--timeout")
            assert cmd[timeout_idx + 1] == "120000"


class TestMochaLineProfileCommand:
    """Tests for building Mocha line profile commands."""

    def test_basic_command(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_line_profile_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_line_profile_command(test_files=[test_file])
            assert "npx" in cmd
            assert "mocha" in cmd
            assert "--exit" in cmd

    def test_timeout_conversion(self):
        from codeflash.languages.javascript.mocha_runner import _build_mocha_line_profile_command

        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "test.js"
            test_file.write_text("// test")

            cmd = _build_mocha_line_profile_command(test_files=[test_file], timeout=45)
            timeout_idx = cmd.index("--timeout")
            assert cmd[timeout_idx + 1] == "45000"


class TestRunMochaBehavioralTests:
    """Tests for running Mocha behavioral tests with mocked subprocess."""

    @patch("codeflash.languages.javascript.mocha_runner.subprocess.run")
    @patch("codeflash.languages.javascript.mocha_runner._ensure_runtime_files")
    def test_sets_codeflash_env_vars(self, mock_ensure, mock_run):
        from codeflash.languages.javascript.mocha_runner import run_mocha_behavioral_tests
        from codeflash.models.models import TestFile, TestFiles
        from codeflash.models.test_type import TestType

        mocha_output = json.dumps(
            {
                "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 10},
                "tests": [{"title": "t", "fullTitle": "s t", "duration": 10, "err": {}}],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )
        mock_run.return_value = MagicMock(returncode=0, stdout=mocha_output, stderr="", args=[])

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            (tmpdir_path / "package.json").write_text('{"name": "test"}')
            test_file = tmpdir_path / "test.test.js"
            test_file.write_text("// test")

            test_paths = TestFiles(
                test_files=[
                    TestFile(
                        original_file_path=test_file,
                        instrumented_behavior_file_path=test_file,
                        benchmarking_file_path=test_file,
                        test_type=TestType.GENERATED_REGRESSION,
                    )
                ]
            )

            result_file, result, cov, _ = run_mocha_behavioral_tests(
                test_paths=test_paths, test_env={}, cwd=tmpdir_path, candidate_index=3
            )

            # Verify env vars were passed
            call_kwargs = mock_run.call_args
            env = call_kwargs.kwargs.get("env") or call_kwargs[1].get("env", {})
            assert env.get("CODEFLASH_MODE") == "behavior"
            assert env.get("CODEFLASH_TEST_ITERATION") == "3"
            assert env.get("CODEFLASH_RANDOM_SEED") == "42"

    @patch("codeflash.languages.javascript.mocha_runner.subprocess.run")
    @patch("codeflash.languages.javascript.mocha_runner._ensure_runtime_files")
    def test_returns_none_coverage(self, mock_ensure, mock_run):
        from codeflash.languages.javascript.mocha_runner import run_mocha_behavioral_tests
        from codeflash.models.models import TestFile, TestFiles
        from codeflash.models.test_type import TestType

        mocha_output = json.dumps(
            {
                "stats": {"tests": 0, "passes": 0, "failures": 0, "duration": 0},
                "tests": [],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )
        mock_run.return_value = MagicMock(returncode=0, stdout=mocha_output, stderr="", args=[])

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            (tmpdir_path / "package.json").write_text('{"name": "test"}')
            test_file = tmpdir_path / "test.test.js"
            test_file.write_text("// test")

            test_paths = TestFiles(
                test_files=[
                    TestFile(
                        original_file_path=test_file,
                        instrumented_behavior_file_path=test_file,
                        benchmarking_file_path=test_file,
                        test_type=TestType.GENERATED_REGRESSION,
                    )
                ]
            )

            _, _, coverage_path, _ = run_mocha_behavioral_tests(test_paths=test_paths, test_env={}, cwd=tmpdir_path)
            assert coverage_path is None


class TestRunMochaBenchmarkingTests:
    """Tests for running Mocha benchmarking tests with mocked subprocess."""

    @patch("codeflash.languages.javascript.mocha_runner.subprocess.run")
    @patch("codeflash.languages.javascript.mocha_runner._ensure_runtime_files")
    def test_sets_perf_env_vars(self, mock_ensure, mock_run):
        from codeflash.languages.javascript.mocha_runner import run_mocha_benchmarking_tests
        from codeflash.models.models import TestFile, TestFiles
        from codeflash.models.test_type import TestType

        mocha_output = json.dumps(
            {
                "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 100},
                "tests": [{"title": "perf", "fullTitle": "bench perf", "duration": 100, "err": {}}],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )
        mock_run.return_value = MagicMock(returncode=0, stdout=mocha_output, stderr="", args=[])

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            (tmpdir_path / "package.json").write_text('{"name": "test"}')
            test_file = tmpdir_path / "perf.test.js"
            test_file.write_text("// perf test")

            test_paths = TestFiles(
                test_files=[
                    TestFile(
                        original_file_path=test_file,
                        instrumented_behavior_file_path=test_file,
                        benchmarking_file_path=test_file,
                        test_type=TestType.GENERATED_REGRESSION,
                    )
                ]
            )

            run_mocha_benchmarking_tests(
                test_paths=test_paths,
                test_env={},
                cwd=tmpdir_path,
                min_loops=3,
                max_loops=50,
                target_duration_ms=5000,
                stability_check=False,
            )

            call_kwargs = mock_run.call_args
            env = call_kwargs.kwargs.get("env") or call_kwargs[1].get("env", {})
            assert env.get("CODEFLASH_MODE") == "performance"
            assert env.get("CODEFLASH_PERF_LOOP_COUNT") == "50"
            assert env.get("CODEFLASH_PERF_MIN_LOOPS") == "3"
            assert env.get("CODEFLASH_PERF_TARGET_DURATION_MS") == "5000"
            assert env.get("CODEFLASH_PERF_STABILITY_CHECK") == "false"


class TestSanitizeMochaImports:
    """Tests for stripping wrong framework imports from Mocha tests."""

    def test_strips_vitest_import(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "import { describe, test, expect, vi } from 'vitest'\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "vitest" not in result
        assert "const x = 1;" in result

    def test_strips_jest_globals_import(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "import { jest, describe, it, expect } from '@jest/globals'\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "@jest/globals" not in result
        assert "const x = 1;" in result

    def test_strips_mocha_require(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "const { describe, it, expect } = require('mocha');\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "require('mocha')" not in result
        assert "const x = 1;" in result

    def test_strips_vitest_comment(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "// vitest imports (REQUIRED for vitest)\nimport { describe } from 'vitest'\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "vitest" not in result
        assert "const x = 1;" in result

    def test_strips_vitest_require_cjs(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "const { describe, test, expect, vi, beforeEach, afterEach } = require('vitest');\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "vitest" not in result
        assert "const x = 1;" in result

    def test_strips_jest_globals_require_cjs(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "const { jest, describe, it } = require('@jest/globals');\nconst x = 1;\n"
        result = sanitize_mocha_imports(source)
        assert "@jest/globals" not in result
        assert "const x = 1;" in result

    def test_strips_vitest_comment_and_cjs_require(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "// vitest imports (REQUIRED for vitest - globals are NOT enabled by default)\nconst { describe, test, expect, vi, beforeEach, afterEach } = require('vitest');\nconst { setCharset } = require('../lib/utils');\n"
        result = sanitize_mocha_imports(source)
        assert "vitest" not in result
        assert "require('../lib/utils')" in result

    def test_preserves_unrelated_imports(self):
        from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports

        source = "const sinon = require('sinon');\nconst assert = require('node:assert/strict');\n"
        result = sanitize_mocha_imports(source)
        assert "sinon" in result
        assert "node:assert/strict" in result


class TestInjectTestGlobalsModuleSystem:
    """Tests for inject_test_globals with different module systems."""

    def test_mocha_esm_uses_import(self):
        from codeflash.languages.javascript.edit_tests import inject_test_globals
        from codeflash.models.models import GeneratedTests, GeneratedTestsList

        tests = GeneratedTestsList(
            generated_tests=[
                GeneratedTests(
                    generated_original_test_source="describe('test', () => {});",
                    instrumented_behavior_test_source="describe('test', () => {});",
                    instrumented_perf_test_source="describe('test', () => {});",
                    behavior_file_path=Path("test.test.js"),
                    perf_file_path=Path("test.perf.test.js"),
                )
            ]
        )

        result = inject_test_globals(tests, test_framework="mocha", module_system="esm")
        assert "import assert from 'node:assert/strict'" in result.generated_tests[0].generated_original_test_source

    def test_mocha_cjs_uses_require(self):
        from codeflash.languages.javascript.edit_tests import inject_test_globals
        from codeflash.models.models import GeneratedTests, GeneratedTestsList

        tests = GeneratedTestsList(
            generated_tests=[
                GeneratedTests(
                    generated_original_test_source="describe('test', () => {});",
                    instrumented_behavior_test_source="describe('test', () => {});",
                    instrumented_perf_test_source="describe('test', () => {});",
                    behavior_file_path=Path("test.test.js"),
                    perf_file_path=Path("test.perf.test.js"),
                )
            ]
        )

        result = inject_test_globals(tests, test_framework="mocha", module_system="commonjs")
        src = result.generated_tests[0].generated_original_test_source
        assert "const assert = require('node:assert/strict')" in src
        assert "import assert" not in src

    def test_vitest_always_uses_import(self):
        from codeflash.languages.javascript.edit_tests import inject_test_globals
        from codeflash.models.models import GeneratedTests, GeneratedTestsList

        tests = GeneratedTestsList(
            generated_tests=[
                GeneratedTests(
                    generated_original_test_source="describe('test', () => {});",
                    instrumented_behavior_test_source="describe('test', () => {});",
                    instrumented_perf_test_source="describe('test', () => {});",
                    behavior_file_path=Path("test.test.js"),
                    perf_file_path=Path("test.perf.test.js"),
                )
            ]
        )

        result = inject_test_globals(tests, test_framework="vitest", module_system="commonjs")
        assert "from 'vitest'" in result.generated_tests[0].generated_original_test_source


class TestEnsureModuleSystemCompatibilityMixed:
    """Tests for ensure_module_system_compatibility with mixed ESM+CJS code."""

    def test_converts_imports_in_mixed_code_to_cjs(self):
        from codeflash.languages.javascript.module_system import ensure_module_system_compatibility

        # Code with both import (from inject_test_globals) and require (from backend)
        code = "import assert from 'node:assert/strict';\nconst { foo } = require('./module');\n"
        result = ensure_module_system_compatibility(code, "commonjs")
        assert "require('node:assert/strict')" in result
        assert "import assert" not in result

    def test_converts_require_in_mixed_code_to_esm(self):
        from codeflash.languages.javascript.module_system import ensure_module_system_compatibility

        code = "import { describe } from 'vitest';\nconst foo = require('./module');\n"
        result = ensure_module_system_compatibility(code, "esm")
        assert "require" not in result
        assert "import" in result

    def test_pure_esm_to_cjs(self):
        from codeflash.languages.javascript.module_system import ensure_module_system_compatibility

        code = "import assert from 'node:assert/strict';\nimport { foo } from './module';\n"
        result = ensure_module_system_compatibility(code, "commonjs")
        assert "require('node:assert/strict')" in result
        assert "import" not in result


class TestRunMochaLineProfileTests:
    """Tests for running Mocha line profile tests with mocked subprocess."""

    @patch("codeflash.languages.javascript.mocha_runner.subprocess.run")
    @patch("codeflash.languages.javascript.mocha_runner._ensure_runtime_files")
    def test_sets_line_profile_env_vars(self, mock_ensure, mock_run):
        from codeflash.languages.javascript.mocha_runner import run_mocha_line_profile_tests
        from codeflash.models.models import TestFile, TestFiles
        from codeflash.models.test_type import TestType

        mocha_output = json.dumps(
            {
                "stats": {"tests": 0, "passes": 0, "failures": 0, "duration": 0},
                "tests": [],
                "passes": [],
                "failures": [],
                "pending": [],
            }
        )
        mock_run.return_value = MagicMock(returncode=0, stdout=mocha_output, stderr="", args=[])

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)
            (tmpdir_path / "package.json").write_text('{"name": "test"}')
            test_file = tmpdir_path / "test.test.js"
            test_file.write_text("// test")
            profile_output = tmpdir_path / "profile.json"

            test_paths = TestFiles(
                test_files=[
                    TestFile(
                        original_file_path=test_file,
                        instrumented_behavior_file_path=test_file,
                        benchmarking_file_path=test_file,
                        test_type=TestType.GENERATED_REGRESSION,
                    )
                ]
            )

            run_mocha_line_profile_tests(
                test_paths=test_paths, test_env={}, cwd=tmpdir_path, line_profile_output_file=profile_output
            )

            call_kwargs = mock_run.call_args
            env = call_kwargs.kwargs.get("env") or call_kwargs[1].get("env", {})
            assert env.get("CODEFLASH_MODE") == "line_profile"
            assert env.get("CODEFLASH_LINE_PROFILE_OUTPUT") == str(profile_output)


class TestParserUnknownTestNameFallback:
    """Tests for the parser's fallback when perf markers have 'unknown' test name."""

    def test_unknown_markers_matched_to_first_testcase(self):
        """When capturePerf markers have 'unknown' test name (Vitest beforeEach not firing),
        the parser should still match them to testcases via the fallback logic.
        """
        from codeflash.languages.javascript.parse import parse_jest_test_xml
        from codeflash.models.models import TestFile, TestFiles
        from codeflash.models.test_type import TestType

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir_path = Path(tmpdir)

            # Create a JUnit XML with one test suite and one testcase
            xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="src/test_func__perf_test_0.test.ts" tests="1" failures="0" time="10.5">
    <testcase name="should compute correctly" classname="src/test_func__perf_test_0.test.ts" time="10.5">
    </testcase>
  </testsuite>
</testsuites>"""
            xml_path = tmpdir_path / "results.xml"
            xml_path.write_text(xml_content, encoding="utf-8")

            # Create test files
            test_file = tmpdir_path / "test_func__perf_test_0.test.ts"
            test_file.write_text("// perf test", encoding="utf-8")

            test_files = TestFiles(
                test_files=[
                    TestFile(
                        instrumented_behavior_file_path=test_file,
                        benchmarking_file_path=test_file,
                        test_type=TestType.GENERATED_REGRESSION,
                    )
                ]
            )

            # Create a mock subprocess result with perf markers using "unknown" test name
            # This simulates what happens when Vitest's beforeEach doesn't fire
            markers = []
            for i in range(1, 6):
                markers.append(f"!######test_mod:unknown:computeFunc:{i}:1_0:{1000 + i * 100}######!")
            stdout = "\n".join(markers)

            mock_result = MagicMock()
            mock_result.stdout = stdout

            test_config = MagicMock()
            test_config.tests_project_rootdir = tmpdir_path
            test_config.test_framework = "vitest"

            results = parse_jest_test_xml(
                test_xml_file_path=xml_path, test_files=test_files, test_config=test_config, run_result=mock_result
            )

            # The "unknown" fallback should assign all 5 markers to the testcase
            assert len(results.test_results) == 5
            # Verify runtimes were extracted (not the 10.5s XML fallback)
            runtimes = [r.runtime for r in results.test_results if r.runtime is not None]
            assert len(runtimes) == 5
            assert all(r < 100_000 for r in runtimes)  # All under 100 microseconds (nanoseconds)
