"""Tests for JavaScript/TypeScript test generation."""

from core.languages.js_ts.testgen import _is_valid_js_identifier, _resolve_import, build_javascript_prompt


class TestIsValidJsIdentifier:
    def test_valid_identifier(self) -> None:
        assert _is_valid_js_identifier("MyClass") is True
        assert _is_valid_js_identifier("validName") is True
        assert _is_valid_js_identifier("_private") is True
        assert _is_valid_js_identifier("name123") is True

    def test_dollar_sign_identifiers(self) -> None:
        assert _is_valid_js_identifier("$handler") is True
        assert _is_valid_js_identifier("$") is True
        assert _is_valid_js_identifier("$scope") is True
        assert _is_valid_js_identifier("jQuery$") is True
        assert _is_valid_js_identifier("$_private") is True
        assert _is_valid_js_identifier("$$double") is True

    def test_reserved_words(self) -> None:
        assert _is_valid_js_identifier("module") is False
        assert _is_valid_js_identifier("exports") is False
        assert _is_valid_js_identifier("prototype") is False
        assert _is_valid_js_identifier("constructor") is False
        assert _is_valid_js_identifier("class") is False
        assert _is_valid_js_identifier("function") is False

    def test_invalid_identifiers(self) -> None:
        assert _is_valid_js_identifier("123invalid") is False
        assert _is_valid_js_identifier("has-dash") is False
        assert _is_valid_js_identifier("has space") is False


class TestResolveImport:
    def test_simple_function(self) -> None:
        assert _resolve_import("execMongoEval", "../ctl/utils") == ("named", "execMongoEval", "execMongoEval")

    def test_class_method(self) -> None:
        assert _resolve_import("Validator.validateRequest", "../middlewares/Validator") == (
            "named",
            "Validator",
            "Validator.validateRequest",
        )

    def test_class_method_default_export(self) -> None:
        assert _resolve_import(
            "Validator.validateRequest",
            "../middlewares/Validator",
            "export default class Validator { validateRequest() {} }",
        ) == ("default", "Validator", "Validator.validateRequest")

    def test_static_method(self) -> None:
        assert _resolve_import("Utils.formatDate", "../utils/Utils") == ("named", "Utils", "Utils.formatDate")

    def test_function_with_underscore(self) -> None:
        assert _resolve_import("get_git_root", "../utils/git") == ("named", "get_git_root", "get_git_root")

    def test_nested_class_method_falls_back_to_namespace(self) -> None:
        assert _resolve_import("Constructor.prototype.method", "../utils/Constructor") == (
            "namespace",
            "Constructor",
            "Constructor.prototype.method",
        )

    def test_reserved_word_class_name_falls_back(self) -> None:
        assert _resolve_import("module.exports", "../utils") == ("namespace", "utils", "module.exports")

    def test_prototype_pattern_falls_back(self) -> None:
        assert _resolve_import("Array.prototype.map", "../polyfills") == (
            "namespace",
            "polyfills",
            "Array.prototype.map",
        )

    def test_exports_reserved_word(self) -> None:
        assert _resolve_import("exports.helper", "../utils") == ("namespace", "utils", "exports.helper")

    def test_invalid_module_name_uses_mod(self) -> None:
        assert _resolve_import("a.b.c", "../123-bad") == ("namespace", "mod", "a.b.c")


class TestBuildJavascriptPrompt:
    # --- Sync / CJS / OpenAI (default) ---

    def test_sync_cjs_openai_system_prompt(self) -> None:
        messages, suffix = build_javascript_prompt(
            function_name="myFunc",
            function_code="function myFunc() {}",
            module_path="../lib/utils",
            test_framework="jest",
            is_async=False,
        )
        assert suffix == ""
        system = messages[0]["content"]
        assert isinstance(system, str)
        assert system == (
            "\nYou are Codeflash, a world-class JavaScript/TypeScript developer with an eagle eye"
            " for unintended bugs and edge cases. You write careful, accurate unit tests."
            " When asked to reply only with code, you write all of your code in a single markdown"
            " code block.\n"
            "\n## Task\n"
            "\nYour task is to create comprehensive, high quality test cases for the myFunc function."
            " These test cases should encompass Basic, Edge, and Large Scale scenarios to ensure the"
            " code's robustness, reliability, and scalability.\n"
            "\n\n## Test Categories\n"
            "\n\n**1. Basic Test Cases**:\n"
            "- **Objective**: To verify the fundamental functionality of the myFunc function under normal conditions.\n"
            "\n**2. Edge Test Cases**:\n"
            "- **Objective**: To evaluate the function's behavior under extreme or unusual conditions.\n"
            "\n**3. Large Scale Test Cases**:\n"
            "- **Objective**: To assess the function's performance and scalability with large data samples.\n"
            "\n\n## Instructions\n"
            "\n- Implement a comprehensive set of test cases following the guidelines above.\n"
            "- Use the testing framework specified in the user message with `describe`, `test`/`it`, and `expect`.\n"
            "- Ensure each test case is well-documented with comments explaining the scenario it covers.\n"
            "- Pay special attention to edge cases as they often reveal hidden bugs.\n"
            "- For large-scale tests, focus on the function's efficiency and performance under heavy loads."
            " Avoid loops exceeding 1000 iterations, and keep data structures under 1000 elements.\n"
            "\n"
            "- **CRITICAL: DO NOT MOCK THE FUNCTION UNDER TEST** - Never mock, stub, or spy on the"
            " myFunc function itself. You may mock external dependencies (APIs, databases, network calls,"
            " file I/O, etc.) if necessary, but the function being tested must execute with its real"
            " implementation.\n"
            "- **CRITICAL: IMPORT FROM REAL MODULES** - Import the function and any related"
            " classes/utilities from their actual module paths as shown in the context.\n"
            "- **CRITICAL: HANDLE ASYNC PROPERLY** - If the function is async, use `async/await` in your"
            " tests and ensure all promises are properly awaited.\n"
            "\n\n## Import Path Rules\n"
            "\n- **NEVER add file extensions (.js, .ts, .tsx) to import paths** - The test framework"
            " resolves extensions automatically.\n"
            "- **WRONG**: `import { fn } from '../utils.js'` or `import { fn } from '../utils.ts'`\n"
            "- **CORRECT**: `import { fn } from '../utils'`\n"
            "- The user message provides the exact import statement to use - copy it exactly without modification.\n"
            "\n## Vitest Imports\n"
            "\n- If test_framework is \"vitest\", you MUST import test functions from 'vitest' since globals"
            " are NOT enabled by default:\n"
            "  ```javascript\n"
            "  import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';\n"
            "  ```\n"
            '- For "jest", globals are typically enabled so no import is needed.\n'
            "\n## Mocking Rules\n"
            "\n- **USE THE CORRECT MOCK SYNTAX FOR THE SPECIFIED FRAMEWORK**:\n"
            "  - For **Jest**: Use `jest.mock()` and `jest.fn()`\n"
            "  - For **Vitest**: Use `vi.mock()` and `vi.fn()` - NEVER use jest.mock with Vitest!\n"
            "- Mock calls are HOISTED to the top of the file. NEVER use dynamic expressions"
            " - use static string literals only.\n"
            "- **ALWAYS use static string literals** for mock paths.\n"
            "- **IMPORTANT**: Check the test framework specified in the user message and use"
            " the matching syntax.\n"
            "\n## Output Format\n"
            "\n- Your response MUST be a single markdown code block containing valid JavaScript/TypeScript code.\n"
            "- Do NOT nest code blocks inside each other.\n"
            "- The code block MUST contain at least one test using `test()` or `it()`.\n"
            "- Follow the exact template structure provided in the user message.\n\n"
        )

    def test_sync_cjs_openai_user_prompt(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="myFunc",
            function_code="function myFunc() {}",
            module_path="../lib/utils",
            test_framework="jest",
            is_async=False,
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "const { myFunc } = require('../lib/utils');" in user
        assert "describe('myFunc'" in user
        assert "require(" in user

    # --- Sync / CJS / Anthropic ---

    def test_sync_cjs_anthropic_system_prompt(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="myFunc",
            function_code="function myFunc() {}",
            module_path="../lib/utils",
            test_framework="jest",
            is_async=False,
            model_type="anthropic",
        )
        system = messages[0]["content"]
        assert isinstance(system, str)
        assert "<role>" in system
        assert "<task>" in system
        assert "<test_categories>" in system
        assert "<instructions>" in system
        assert "<rules>" in system
        assert "<output_format>" in system
        assert "## Task" not in system
        assert "## Instructions" not in system

    # --- Async / CJS / OpenAI ---

    def test_async_cjs_openai_system_prompt(self) -> None:
        messages, suffix = build_javascript_prompt(
            function_name="fetchData",
            function_code="async function fetchData() {}",
            module_path="../api",
            test_framework="jest",
            is_async=True,
            model_type="openai",
        )
        assert suffix == "async-"
        system = messages[0]["content"]
        assert isinstance(system, str)
        assert "for **asynchronous** code" in system
        assert "**async** fetchData" in system
        assert "with proper async/await handling" in system
        assert "### CRITICAL: ASYNC TEST REQUIREMENTS" in system
        assert "TESTS MUST BE DETERMINISTIC" in system
        assert "TEST REJECTION CASES" in system
        assert "async test using `test('...', async () => ...)`" in system

    def test_async_cjs_openai_user_prompt(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="fetchData",
            function_code="async function fetchData() {}",
            module_path="../api",
            test_framework="jest",
            is_async=True,
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "write a test suite for the following **ASYNC** JavaScript function." in user
        assert "**CRITICAL: This function is ASYNCHRONOUS**" in user
        assert "All calls to fetchData MUST be awaited: `await fetchData(...)`" in user
        assert "const { fetchData } = require('../api');" in user
        assert "const result = await fetchData(/* args */);" in user
        assert "await expect(fetchData(/* invalid args */)).rejects.toThrow();" in user

    # --- Async / CJS / Anthropic ---

    def test_async_cjs_anthropic_system_prompt(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="fetchData",
            function_code="async function fetchData() {}",
            module_path="../api",
            test_framework="jest",
            is_async=True,
            model_type="anthropic",
        )
        system = messages[0]["content"]
        assert isinstance(system, str)
        assert "<role>" in system
        assert "for **asynchronous** code" in system
        assert "<async_requirements>" in system
        assert "TESTS MUST BE DETERMINISTIC" in system
        assert "TEST REJECTION CASES" in system
        assert "<output_format>" in system
        assert "async test using `test('...', async () => ...)`" in system

    # --- ESM ---

    def test_esm_named_import(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="myFunc",
            function_code="function myFunc() {}",
            module_path="../lib/utils",
            test_framework="vitest",
            is_async=False,
            module_system="esm",
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "import { myFunc } from '../lib/utils';" in user
        assert "require(" not in user

    def test_esm_default_import(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="Svc.run",
            function_code="export default class Svc { run() {} }",
            module_path="../services/Svc",
            test_framework="vitest",
            is_async=False,
            module_system="esm",
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "import Svc from '../services/Svc';" in user

    def test_esm_async(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="fetchData",
            function_code="async function fetchData() {}",
            module_path="../api",
            test_framework="vitest",
            is_async=True,
            module_system="esm",
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "import { fetchData } from '../api';" in user
        assert "const result = await fetchData(/* args */);" in user

    # --- CJS (class methods) ---

    def test_cjs_class_method(self) -> None:
        messages, _ = build_javascript_prompt(
            function_name="Validator.validateRequest",
            function_code="export default class Validator { validateRequest() {} }",
            module_path="../middlewares/Validator",
            test_framework="jest",
            is_async=False,
        )
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "const Validator = require('../middlewares/Validator');" in user
        assert "describe('Validator.validateRequest'" in user
        assert "{ Validator.validateRequest }" not in user

    def test_cjs_class_method_async(self) -> None:
        messages, suffix = build_javascript_prompt(
            function_name="Controller.asyncMethod",
            function_code="export default class Controller { async asyncMethod() {} }",
            module_path="../controllers/Controller",
            test_framework="jest",
            is_async=True,
        )
        assert suffix == "async-"
        user = messages[1]["content"]
        assert isinstance(user, str)
        assert "const Controller = require('../controllers/Controller');" in user
