"""Utilities for detecting test functions and test classes.

Provides unified functions that work with both ast and libcst nodes.
"""

from __future__ import annotations

import ast

import libcst as cst


def is_test_function_name(name: str) -> bool:
    """Check if a name follows test function naming convention."""
    return name.startswith("test_")


def is_test_class_name(name: str) -> bool:
    """Check if a name follows test class naming convention."""
    return name.startswith("Test") or name.endswith(("Test", "TestCase"))


def is_test_function(node: ast.stmt | cst.FunctionDef) -> bool:
    """Check if a node is a test function (works with both ast and libcst)."""
    if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
        return is_test_function_name(node.name)
    if isinstance(node, cst.FunctionDef):
        return is_test_function_name(node.name.value)
    return False


def is_test_class(node: ast.stmt | cst.ClassDef) -> bool:
    """Check if a node is a test class (works with both ast and libcst).

    A class is considered a test class if it:
    - Has a name matching test class conventions (Test* or *Test or *TestCase)
    - Inherits from TestCase
    - Contains test methods (CST only, for classes that may not follow naming conventions)
    """
    if isinstance(node, ast.ClassDef):
        return is_test_class_name(node.name) or inherits_from_testcase_ast(node)
    if isinstance(node, cst.ClassDef):
        return (
            is_test_class_name(node.name.value) or inherits_from_testcase_cst(node) or class_contains_test_methods(node)
        )
    return False


def inherits_from_testcase(node: ast.ClassDef | cst.ClassDef) -> bool:
    """Check if a class inherits from TestCase (works with both ast and libcst)."""
    if isinstance(node, ast.ClassDef):
        return inherits_from_testcase_ast(node)
    if isinstance(node, cst.ClassDef):
        return inherits_from_testcase_cst(node)
    return False


def class_contains_test_methods(node: cst.ClassDef) -> bool:
    """Check if a libcst class contains any test methods."""
    return any(isinstance(stmt, cst.FunctionDef) and is_test_function_name(stmt.name.value) for stmt in node.body.body)


def inherits_from_testcase_ast(node: ast.ClassDef) -> bool:
    """Check if an AST class inherits from TestCase."""
    return any(
        (isinstance(base, ast.Name) and base.id == "TestCase")
        or (isinstance(base, ast.Attribute) and base.attr == "TestCase")
        for base in node.bases
    )


def inherits_from_testcase_cst(node: cst.ClassDef) -> bool:
    """Check if a libcst class inherits from TestCase."""
    return any(
        (isinstance(base.value, cst.Name) and base.value.value in ("TestCase", "unittest.TestCase"))
        or (isinstance(base.value, cst.Attribute) and base.value.attr.value == "TestCase")
        for base in node.bases
    )
