"""Tests for JavaScript syntax validation."""

from aiservice.validators.javascript_validator import validate_javascript_syntax, validate_typescript_syntax


class TestJavaScriptValidatorFallback:
    """Tests for the fallback validation (when Node.js is not available)."""

    def test_valid_simple_function(self) -> None:
        """Test that a simple valid function passes validation."""
        code = """
function add(a, b) {
    return a + b;
}
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_valid_arrow_function(self) -> None:
        """Test that arrow functions pass validation."""
        code = "const add = (a, b) => a + b;"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_valid_class(self) -> None:
        """Test that a class definition passes validation."""
        code = """
class Calculator {
    constructor() {
        this.result = 0;
    }

    add(value) {
        this.result += value;
        return this;
    }
}
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_valid_async_function(self) -> None:
        """Test that async functions pass validation."""
        code = """
async function fetchData(url) {
    const response = await fetch(url);
    return response.json();
}
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_valid_template_literal(self) -> None:
        """Test that template literals pass validation."""
        code = """
const greeting = `Hello, ${name}!`;
const multiline = `
    This is a
    multiline string
`;
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_unclosed_brace(self) -> None:
        """Test that unclosed braces are detected."""
        code = """
function broken() {
    return true;
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_unclosed_parenthesis(self) -> None:
        """Test that unclosed parentheses are detected."""
        code = "const result = add(1, 2"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_unclosed_bracket(self) -> None:
        """Test that unclosed brackets are detected."""
        code = "const arr = [1, 2, 3"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_mismatched_brackets(self) -> None:
        """Test that mismatched brackets are detected."""
        code = "const arr = [1, 2, 3}"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_string_with_brackets_ignored(self) -> None:
        """Test that brackets inside strings are ignored."""
        code = """
const str = "This string has { and } and [ and ]";
const obj = { valid: true };
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_empty_code(self) -> None:
        """Test that empty code passes validation."""
        is_valid, error = validate_javascript_syntax("")
        assert is_valid is True
        assert error is None

    def test_comments_only(self) -> None:
        """Test that code with only comments passes validation."""
        code = """
// This is a comment
/* This is a
   multiline comment */
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_nested_structures(self) -> None:
        """Test that deeply nested structures pass validation."""
        code = """
const nested = {
    level1: {
        level2: {
            level3: [
                { deep: true }
            ]
        }
    }
};
"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None


class TestJavaScriptValidatorMain:
    """Tests for the main validation function."""

    def test_valid_module_exports(self) -> None:
        """Test that module.exports syntax passes validation."""
        code = """
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

module.exports = { fibonacci };
"""
        is_valid, error = validate_javascript_syntax(code)
        # This may pass with fallback even if Node.js isn't available
        # The point is it shouldn't crash
        assert isinstance(is_valid, bool)

    def test_valid_es6_imports(self) -> None:
        """Test that ES6 import syntax passes validation."""
        code = """
import { useState, useEffect } from 'react';

export function Counter() {
    const [count, setCount] = useState(0);
    return count;
}
"""
        is_valid, error = validate_javascript_syntax(code)
        assert isinstance(is_valid, bool)

    def test_destructuring(self) -> None:
        """Test that destructuring passes validation."""
        code = """
const { a, b } = obj;
const [first, second, ...rest] = arr;
"""
        is_valid, error = validate_javascript_syntax(code)
        assert isinstance(is_valid, bool)

    def test_spread_operator(self) -> None:
        """Test that spread operator passes validation."""
        code = """
const merged = { ...obj1, ...obj2 };
const combined = [...arr1, ...arr2];
"""
        is_valid, error = validate_javascript_syntax(code)
        assert isinstance(is_valid, bool)


class TestTypeScriptValidator:
    """Tests for TypeScript validation."""

    def test_typescript_types(self) -> None:
        """Test that TypeScript type annotations pass validation."""
        code = """
function add(a: number, b: number): number {
    return a + b;
}
"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_typescript_type_assertion_valid_in_ts(self) -> None:
        """Test that TypeScript type assertions are valid in TypeScript."""
        code = "const value = 4.9 as unknown as number;"
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_typescript_type_assertion_invalid_in_js(self) -> None:
        """Test that TypeScript type assertions are INVALID in JavaScript.

        This is a critical test - TypeScript-specific syntax like 'as unknown as number'
        should fail when validated as JavaScript. This bug caused production issues
        where generated TypeScript tests were incorrectly validated with JS parser.
        """
        code = "const value = 4.9 as unknown as number;"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_typescript_generic_valid_in_ts(self) -> None:
        """Test that TypeScript generics are valid in TypeScript."""
        code = "function identity<T>(arg: T): T { return arg; }"
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_typescript_generic_invalid_in_js(self) -> None:
        """Test that TypeScript generics are INVALID in JavaScript."""
        code = "function identity<T>(arg: T): T { return arg; }"
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_typescript_interface(self) -> None:
        """Test that TypeScript interfaces pass validation (if Node available)."""
        code = """
interface User {
    name: string;
    age: number;
}

function greet(user: User): string {
    return `Hello, ${user.name}`;
}
"""
        is_valid, error = validate_typescript_syntax(code)
        assert isinstance(is_valid, bool)

    def test_markdown_code(self) -> None:
        code = """```typescript:generateCorrelationId.ts
import { randomUUID } from "crypto"

export function generateCorrelationId(service: string = "cf-api"): string {
  const timestamp = Date.now().toString(36)
  const random = Math.random().toString(36).substring(2, 8)
  return `${service}-${timestamp}-${random}`
}
```
"""
        is_valid, error = validate_typescript_syntax(code)
        assert isinstance(is_valid, bool)
        assert is_valid
        assert error is None

    def test_markdown_code_with_error(self) -> None:
        code = """```typescript:generateCorrelationId.ts
import { randomUUID } from "crypto"

export function generateCorrelationId(service: string = "cf-api"): string {
  const timestamp = Date.now().toString(36)
  const random = Math.random().toString(36).substring(2, 8)
  return `${service}-${timestamp}-${random}`
}
```
```typescript:generateCorrelationId_1.ts
import { randomUUID } from "crypto"

export function generateCorrelationId(service: string default "cf-api"): string {
  const timestamp = Date.now().toString(36)
  const random = Math.random().toString(36).substring(2, 8)
  return `${service}-${timestamp}-${random}`
}
```
"""
        is_valid, error = validate_typescript_syntax(code)
        assert isinstance(is_valid, bool)
        assert not is_valid
        assert error is not None


class TestErrorLocationReporting:
    """Tests for error location reporting in validation errors."""

    def test_error_includes_line_number(self) -> None:
        """Test that syntax errors include line number in error message."""
        code = """function broken( {
    return 123;
}"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None
        assert "line" in error.lower()

    def test_error_includes_code_snippet(self) -> None:
        """Test that syntax errors include code snippet in error message."""
        code = """function broken( {
    return 123;
}"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None
        # Error should contain part of the problematic line
        assert "broken" in error or "function" in error

    def test_typescript_error_includes_line_number(self) -> None:
        """Test that TypeScript syntax errors include line number."""
        code = """interface User {
    name: string
    age number  // missing colon
}"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is False
        assert error is not None
        assert "line" in error.lower()

    def test_markdown_error_includes_line_number(self) -> None:
        """Test that errors in markdown code blocks include line number."""
        code = """```typescript:test.ts
function valid(): string {
    return "hello";
}

function broken( {
    return 123;
}
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is False
        assert error is not None
        assert "line" in error.lower()

    def test_error_on_specific_line(self) -> None:
        """Test that error reports correct line number for error on line 3."""
        code = """const a = 1;
const b = 2;
const c = broken(;
const d = 4;"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is False
        assert error is not None
        # Error should be on line 3
        assert "line 3" in error.lower() or "line 3," in error

    def test_typescript_async_function_with_template_literal(self) -> None:
        """Test that async functions with template literals validate correctly."""
        code = """```typescript:src/ctl/mongo_shell_utils.ts
import * as utils from "./utils";

const command_args = process.argv.slice(3);

async function execMongoEval(queryExpression, appsmithMongoURI) {
  queryExpression = queryExpression.trim();

  if (command_args.includes("--pretty")) {
    queryExpression += ".pretty()";
  }

  return await utils.execCommand([
    "mongosh",
    appsmithMongoURI,
    `--eval=${queryExpression}`,
  ]);
}
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_typescript_try_catch_function(self) -> None:
        """Test that functions with try-catch blocks validate correctly."""
        code = """```typescript:src/ctl/restore.ts
import fsPromises from "fs/promises";
import path from "path";

async function figureOutContentsPath(root: string): Promise<string> {
  const subfolders = await fsPromises.readdir(root, { withFileTypes: true });

  try {
    await fsPromises.access(path.join(root, "manifest.json"));
    return root;
  } catch (error) {
    // Ignore
  }

  for (const subfolder of subfolders) {
    if (subfolder.isDirectory()) {
      try {
        await fsPromises.access(
          path.join(root, subfolder.name, "manifest.json"),
        );
        return path.join(root, subfolder.name);
      } catch (error) {
        // Ignore
      }
    }
  }

  throw new Error("Could not find the contents.");
}
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None


class TestMarkdownParsing:
    """Tests for markdown code block parsing in validation."""

    def test_empty_markdown_no_code_blocks(self) -> None:
        """Test validation when markdown has no matching code blocks."""
        # This markdown has python blocks, not typescript
        # Note: Raw markdown with ``` actually parses as valid TypeScript
        # because backticks form template literals in TypeScript/JavaScript
        code = """```python
def hello():
    return "world"
```"""
        # When no typescript blocks found, it should fall through to validate raw
        is_valid, error = validate_typescript_syntax(code)
        # The raw markdown happens to be valid TypeScript (template literals)
        # This verifies the warning is logged and fallback validation runs
        assert is_valid is True
        assert error is None

    def test_multiple_valid_code_blocks(self) -> None:
        """Test that multiple valid code blocks all pass validation."""
        code = """```typescript:file1.ts
function add(a: number, b: number): number {
    return a + b;
}
```
```typescript:file2.ts
function multiply(a: number, b: number): number {
    return a * b;
}
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_one_invalid_block_fails_all(self) -> None:
        """Test that one invalid block in multiple blocks fails validation."""
        code = """```typescript:valid.ts
function valid(): number {
    return 42;
}
```
```typescript:invalid.ts
function invalid( {
    return broken;
}
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is False
        assert error is not None

    def test_javascript_markdown_blocks(self) -> None:
        """Test JavaScript code in markdown blocks."""
        code = """```javascript:utils.js
function formatDate(date) {
    return date.toISOString();
}
```"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_js_shorthand_in_markdown(self) -> None:
        """Test that 'js' shorthand works in markdown blocks."""
        code = """```js:utils.js
const add = (a, b) => a + b;
```"""
        is_valid, error = validate_javascript_syntax(code)
        assert is_valid is True
        assert error is None

    def test_ts_shorthand_in_markdown(self) -> None:
        """Test that 'ts' shorthand works in markdown blocks."""
        code = """```ts:utils.ts
const add = (a: number, b: number): number => a + b;
```"""
        is_valid, error = validate_typescript_syntax(code)
        assert is_valid is True
        assert error is None
