"""Pattern detection for Python source files."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tree_sitter import Node

from vibelexity.models import PatternViolation
from vibelexity.parsers.python import PY_LANGUAGE
from vibelexity.patterns.common import create_query, run_captures

_PY_NESTED_IMPORT_QUERY = """
(block (import_statement) @nested_import)
(block (import_from_statement) @nested_import)
"""

_PY_WILDCARD_IMPORT_QUERY = "(import_from_statement (wildcard_import) @wildcard)"

_MODULE_LEVEL_QUERY = """
(expression_statement) @exec_stmt
(for_statement) @exec_stmt
(while_statement) @exec_stmt
(with_statement) @exec_stmt
(try_statement) @exec_stmt
(if_statement) @if_stmt
"""


def _is_type_checking_import(node: Node) -> bool:
    """Check if import is inside a 'if TYPE_CHECKING:' block."""
    parent = node.parent
    while parent:
        if parent.type == "if_statement":
            for child in parent.children:
                if child.type in ("identifier", "expression_statement"):
                    text = (
                        child.text
                        if child.type == "identifier"
                        else child.text.decode()
                        if child.text
                        else ""
                    )
                    if text == b"TYPE_CHECKING" or "TYPE_CHECKING" in str(text):
                        return True
        if parent.type == "module":
            break
        parent = parent.parent
    return False


def check_nested_imports(root: Node) -> list[PatternViolation]:
    """Detect import statements not at module level (inside functions/classes)."""
    query = create_query(PY_LANGUAGE, _PY_NESTED_IMPORT_QUERY)
    captures = run_captures(query, root)

    violations = []
    for node, capture_name in captures:
        if _is_type_checking_import(node):
            continue
        line = node.start_point[0] + 1
        violations.append(
            PatternViolation(
                type="nested_import",
                line=line,
                description="Import statement inside function/class body instead of module level",
                severity="warning",
            )
        )

    return violations


def check_wildcard_imports(root: Node) -> list[PatternViolation]:
    """Detect wildcard imports like 'from module import *'."""
    query = create_query(PY_LANGUAGE, _PY_WILDCARD_IMPORT_QUERY)
    captures = run_captures(query, root)

    violations = []
    for node, capture_name in captures:
        if capture_name == "wildcard":
            line = node.start_point[0] + 1
            violations.append(
                PatternViolation(
                    type="wildcard_import",
                    line=line,
                    description="Wildcard import 'from module import *' - explicit imports preferred",
                    severity="warning",
                )
            )

    return violations


def _is_main_guard_condition(if_stmt_node: Node) -> bool:
    """Check if an if_statement matches the 'if __name__ == \"__main__\"' pattern."""
    for child in if_stmt_node.children:
        if child.type == "comparison_operator":
            children = child.children
            if len(children) >= 3:
                left = children[0]
                right = children[2]
                if (
                    left.type == "identifier"
                    and left.is_named
                    and right.type == "string"
                    and right.is_named
                ):
                    left_text = left.text.decode() if left.text else ""
                    right_text = right.text.decode() if right.text else ""
                    right_unquoted = right_text.strip("\"'")
                    return left_text == "__name__" and right_unquoted == "__main__"
    return False


def _is_inside_main_guard(node: Node) -> bool:
    """Check if node is inside an 'if __name__ == \"__main__\"' block."""
    parent = node.parent
    while parent:
        if parent.type == "if_statement":
            if _is_main_guard_condition(parent):
                return True
        parent = parent.parent
    return False


def _is_inside_type_checking_block(node: Node) -> bool:
    """Check if node is inside an 'if TYPE_CHECKING:' block."""
    parent = node.parent
    while parent:
        if parent.type == "if_statement":
            for child in parent.children:
                if child.type == "identifier" or child.type == "expression_statement":
                    text = (
                        child.text
                        if child.type == "identifier"
                        else child.text.decode()
                        if child.text
                        else ""
                    )
                    if text == b"TYPE_CHECKING" or "TYPE_CHECKING" in str(text):
                        return True
        parent = parent.parent
    return False


def _is_type_checking_block_statement(if_stmt_node: Node) -> bool:
    """Check if the if_statement itself is the 'if TYPE_CHECKING:' guard."""
    for child in if_stmt_node.children:
        if child.type == "identifier" and child.is_named:
            text = child.text.decode() if child.text else ""
            if text == "TYPE_CHECKING":
                return True
    return False


def _involves_execution(node: Node) -> bool:
    """Check if a node involves execution (function calls, operations, etc.).

    Returns False for simple literals and identifiers (constants).
    Returns True for anything that involves computation or side effects.
    """
    if not node.is_named:
        return False

    # Types that represent literal values (no execution)
    literal_types = {
        "string",
        "concatenated_string",
        "integer",
        "float",
        "true",
        "false",
        "none",
        "comment",
    }

    if node.type in literal_types:
        return False

    # Simple identifier reference (e.g., `x = y` where y is just a name)
    if node.type == "identifier" and node.parent and node.parent.type == "assignment":
        parent = node.parent
        left = parent.child_by_field_name("left")
        right = parent.child_by_field_name("right")
        if node == right and node.child_count == 0:
            return False

    # Execution types
    exec_types = {"call", "binary_operator", "boolean_operator", "unary_operator"}
    for child in node.children:
        if not child.is_named:
            continue
        if child.type in exec_types or _involves_execution(child):
            return True

    return False


def _is_inside_function_or_class(node: Node) -> bool:
    """Check if node is inside a function or class definition."""
    parent = node.parent
    while parent:
        if parent.type in ("function_definition", "class_definition"):
            return True
        if parent.type == "module":
            return False
        parent = parent.parent
    return False


def _is_literal_only_expression(node: Node) -> bool:
    """Check if expression_statement contains only literals (no execution).

    Returns True if all children are identifier, string, integer, float, true, false, or none.
    These are considered non-executing expressions.
    """
    literal_types = {
        "identifier",
        "string",
        "integer",
        "float",
        "true",
        "false",
        "none",
    }
    for child in node.children:
        if child.type not in literal_types:
            return False
    return True


def check_module_level_execution(root: Node) -> list[PatternViolation]:
    """Detect unconditional module-level code execution.

    Flags code that executes at import time, excluding code inside
    if __name__ == "__main__" or if TYPE_CHECKING: blocks, and constant assignments.
    """
    query = create_query(PY_LANGUAGE, _MODULE_LEVEL_QUERY)
    captures = run_captures(query, root)

    violations = []
    seen_lines = set()

    for node, capture_name in captures:
        # Skip if inside function/class, main guard, type checking, or comment
        if (
            _is_inside_function_or_class(node)
            or _is_inside_main_guard(node)
            or _is_inside_type_checking_block(node)
            or node.type == "comment"
        ):
            continue

        # Skip literal-only expressions
        if capture_name == "exec_stmt" and node.type == "expression_statement":
            if _is_literal_only_expression(node):
                continue

        # Skip invalid if statements
        if capture_name == "if_stmt":
            if _is_main_guard_condition(node) or _is_type_checking_block_statement(
                node
            ):
                continue

        # Skip constant assignments
        if capture_name == "exec_stmt" and node.type == "expression_statement":
            is_constant = False
            for child in node.children:
                if child.type == "assignment" and not _involves_execution(child):
                    is_constant = True
                    break
            if is_constant:
                continue

        line = node.start_point[0] + 1
        if line in seen_lines:
            continue
        seen_lines.add(line)

        violations.append(
            PatternViolation(
                type="module_level_call",
                line=line,
                description="Module-level code execution (runs at import time) - consider moving to a function",
                severity="warning",
            )
        )

    return violations
