"""Integration tests for ESLint core."""

import subprocess
from pathlib import Path

import pytest
from loguru import logger

from lintro.parsers.eslint.eslint_parser import parse_eslint_output
from lintro.tools.implementations.tool_eslint import EslintTool
from lintro.utils.tool_utils import format_tool_output

logger.remove()
logger.add(lambda msg: print(msg, end=""), level="INFO")

SAMPLE_FILE = Path("test_samples/eslint_violations.js")


@pytest.fixture
def temp_eslint_file(tmp_path):
    """Create a temp copy of the sample JS file with violations.

    Args:
        tmp_path: Temporary directory provided by pytest.

    Returns:
        Path to the copied temporary JavaScript file containing violations.
    """
    temp_file = tmp_path / SAMPLE_FILE.name
    temp_file.write_text(SAMPLE_FILE.read_text())
    return temp_file


def run_eslint_directly(
    file_path: Path,
    check_only: bool = True,
) -> tuple[bool, str, int]:
    """Run ESLint directly on a file and return result tuple.

    Args:
        file_path: Path to the file to check/fix.
        check_only: If True, use check mode, else use --fix.

    Returns:
        tuple[bool, str, int]: Tuple of (success, output, issues_count).
    """
    cmd = EslintTool()._get_executable_command("eslint") + [
        "--format",
        "json",
    ]
    if not check_only:
        cmd.append("--fix")
    cmd.append(file_path.name)
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=False,
            cwd=file_path.parent,
            timeout=30,
        )
    except subprocess.TimeoutExpired:
        pytest.fail(
            f"ESLint subprocess timed out after 30s. Command: {' '.join(cmd)}",
        )
    # Combine stdout and stderr for full output
    full_output = result.stdout + result.stderr
    # Count issues using the eslint parser
    issues = parse_eslint_output(full_output)
    issues_count = len(issues)
    success = (
        result.returncode == 0 and issues_count == 0
        if check_only
        else result.returncode == 0
    )
    return success, full_output, issues_count


def test_eslint_reports_violations_direct(temp_eslint_file) -> None:
    """ESLint CLI: Should detect and report violations in a sample file.

    Args:
        temp_eslint_file: Pytest fixture providing a temporary file with violations.
    """
    logger.info("[TEST] Running ESLint directly on sample file...")
    success, output, issues = run_eslint_directly(temp_eslint_file, check_only=True)
    logger.info(f"[LOG] ESLint found {issues} issues. Output:\n{output}")
    # ESLint may return 0 exit code even with warnings, so check issues count
    assert (
        issues > 0 or not success
    ), "ESLint should report issues or fail when violations are present."


def test_eslint_reports_violations_through_lintro(temp_eslint_file) -> None:
    """Lintro EslintTool: Should detect and report violations in a sample file.

    Args:
        temp_eslint_file: Pytest fixture providing a temporary file with violations.
    """
    logger.info("[TEST] Running EslintTool through lintro on sample file...")
    tool = EslintTool()
    tool.set_options()
    result = tool.check([str(temp_eslint_file)])
    logger.info(
        f"[LOG] Lintro EslintTool found {result.issues_count} issues. "
        f"Output:\n{result.output}",
    )
    # Note: ESLint may succeed with warnings, so we check issues_count
    if result.issues_count > 0:
        assert (
            not result.success
        ), "Lintro EslintTool should fail when violations are present."
    assert result.issues_count >= 0, "Lintro EslintTool should report issues count."


def test_eslint_fix_method(temp_eslint_file) -> None:
    """Lintro EslintTool: Should fix auto-fixable issues.

    Args:
        temp_eslint_file: Pytest fixture providing a temporary file with violations.
    """
    logger.info("[TEST] Running EslintTool.fix() on sample file...")
    tool = EslintTool()
    tool.set_options()

    # Check before fixing
    pre_result = tool.check([str(temp_eslint_file)])
    logger.info(
        f"[LOG] Before fix: {pre_result.issues_count} issues. "
        f"Output:\n{pre_result.output}",
    )
    initial_count = pre_result.issues_count

    # Fix issues
    post_result = tool.fix([str(temp_eslint_file)])
    logger.info(
        f"[LOG] After fix: {post_result.remaining_issues_count} remaining issues. "
        f"Output:\n{post_result.output}",
    )

    # Verify that some issues were fixed (or all if all were fixable)
    assert (
        post_result.remaining_issues_count <= initial_count
    ), "Should fix at least some issues or have same count"

    # Verify no issues remain or fewer remain
    final_result = tool.check([str(temp_eslint_file)])
    logger.info(
        f"[LOG] Final check: {final_result.issues_count} issues. "
        f"Output:\n{final_result.output}",
    )
    assert (
        final_result.issues_count <= initial_count
    ), "Should have same or fewer issues after fixing"


def test_eslint_output_consistency_direct_vs_lintro(temp_eslint_file) -> None:
    """ESLint CLI vs Lintro: Should produce consistent results for the same file.

    Args:
        temp_eslint_file: Pytest fixture providing a temporary file with violations.
    """
    logger.info("[TEST] Comparing ESLint CLI and Lintro EslintTool outputs...")
    tool = EslintTool()
    tool.set_options()

    # Run eslint directly
    direct_success, direct_output, direct_issues = run_eslint_directly(
        temp_eslint_file,
        check_only=True,
    )

    # Run through lintro
    result = tool.check([str(temp_eslint_file)])

    logger.info(
        f"[LOG] CLI issues: {direct_issues}, Lintro issues: {result.issues_count}",
    )
    # Issue counts should match (allowing for slight differences due to parsing)
    assert abs(direct_issues - result.issues_count) <= 1, (
        f"Issue count mismatch: CLI={direct_issues}, Lintro={result.issues_count}\n"
        f"CLI Output:\n{direct_output}\nLintro Output:\n{result.output}"
    )


def test_eslint_fix_sets_issues_for_table(temp_eslint_file) -> None:
    """EslintTool.fix should populate issues for table rendering.

    Args:
        temp_eslint_file: Pytest fixture providing a temporary file with violations.
    """
    tool = EslintTool()
    tool.set_options()

    # Ensure there are initial issues
    pre_result = tool.check([str(temp_eslint_file)])
    if pre_result.issues_count == 0:
        pytest.skip("No issues found in test file")

    # Run fix and assert issues are provided for table formatting
    fix_result = tool.fix([str(temp_eslint_file)])
    assert fix_result.issues is not None
    assert isinstance(fix_result.issues, list)
    assert len(fix_result.issues) == fix_result.remaining_issues_count

    # Verify that formatted output renders correctly
    formatted = format_tool_output(
        tool_name="eslint",
        output=fix_result.output or "",
        group_by="auto",
        output_format="grid",
        issues=fix_result.issues,
    )
    assert formatted  # Should produce some output


def test_eslint_respects_eslintignore(tmp_path) -> None:
    """EslintTool: Should respect .eslintignore file.

    Args:
        tmp_path: Pytest temporary directory fixture.
    """
    # Create a file that should be ignored
    ignored_file = tmp_path / "ignored.js"
    ignored_file.write_text("var unused = 1;")  # Should trigger eslint

    # Create a file that should be checked
    checked_file = tmp_path / "checked.js"
    checked_file.write_text("var unused = 1;")  # Should trigger eslint

    # Create .eslintignore
    eslintignore = tmp_path / ".eslintignore"
    eslintignore.write_text("ignored.js\n")

    logger.info("[TEST] Testing eslint with .eslintignore...")
    tool = EslintTool()
    tool.set_options()

    # Check both files - ignored.js should be ignored, checked.js should be checked
    result = tool.check([str(tmp_path)])
    logger.info(
        f"[LOG] ESLint found {result.issues_count} issues. "
        f"Output:\n{result.output}",
    )

    # Should find issues in checked.js but not ignored.js
    # Since we're checking the directory, eslint should respect .eslintignore
    # and only report issues for checked.js
    issue_files = [issue.file for issue in result.issues]
    assert not any(
        "ignored.js" in f for f in issue_files
    ), "ignored.js should be excluded from issue files when .eslintignore is present"
    # Note: ESLint may still report issues if the file path matches differently
    # This test verifies that .eslintignore is being used by ESLint
    assert result.issues_count >= 0, "Should process files"
