"""Pattern detection for repeated calls that could be refactored to loops."""

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

# S-query to find pairs of adjacent expression statements with method calls
# on the same object using the same method name.
# The `.` operator matches immediate siblings (no intervening named nodes).
_PY_REPEATED_CALLS_QUERY = """
(
  (expression_statement
    (call
      function: (attribute
        object: (identifier) @obj1
        attribute: (identifier) @method1))) @call1
  .
  (expression_statement
    (call
      function: (attribute
        object: (identifier) @obj2
        attribute: (identifier) @method2))) @call2
  (#eq? @obj1 @obj2)
  (#eq? @method1 @method2)
)
"""

# S-query to find pairs of adjacent expression statements with standalone function calls.
# The `.` operator matches immediate siblings (no intervening named nodes).
_PY_REPEATED_FUNCTION_CALLS_QUERY = """
(
  (expression_statement
    (call
      function: (identifier) @func1)) @call1
  .
  (expression_statement
    (call
      function: (identifier) @func2)) @call2
  (#eq? @func1 @func2)
)
"""

# Minimum number of consecutive calls to flag as a violation
_MIN_CONSECUTIVE_CALLS = 3


def _get_call_info(node: Node) -> tuple[str, str, str | None, int] | None:
    """Extract call info from an expression_statement containing a call.

    Returns:
        None if the node doesn't match expected patterns.
        For method calls: ("method", "obj_name", "method_name", line)
        For function calls: ("function", "func_name", None, line)
    """
    if node.type != "expression_statement":
        return None

    for child in node.children:
        if child.type == "call":
            for call_child in child.children:
                # Check for method call: object.method()
                if call_child.type == "attribute":
                    obj_name = None
                    method_name = None
                    for attr_child in call_child.children:
                        if attr_child.type == "identifier":
                            if obj_name is None:
                                obj_name = (
                                    attr_child.text.decode() if attr_child.text else ""
                                )
                            else:
                                method_name = (
                                    attr_child.text.decode() if attr_child.text else ""
                                )
                    if obj_name and method_name:
                        line = node.start_point[0] + 1
                        return ("method", obj_name, method_name, line)
                # Check for function call: func()
                elif call_child.type == "identifier":
                    func_name = call_child.text.decode() if call_child.text else ""
                    if func_name:
                        line = node.start_point[0] + 1
                        return ("function", func_name, None, line)
    return None


def _find_consecutive_groups(
    calls: list[tuple[str, str, str | None, int]],
) -> list[list[tuple[str, str, str | None, int]]]:
    """Group calls by target and find consecutive sequences.

    Args:
        calls: List of (call_type, primary, secondary, line) tuples, sorted by line.
              - Method calls: ("method", obj_name, method_name, line)
              - Function calls: ("function", func_name, None, line)

    Returns:
        List of groups, where each group is a list of consecutive calls
        with the same target. Method and function calls are never mixed.
    """
    if not calls:
        return []

    def _get_target(call):
        """Get a comparable target key for grouping."""
        call_type, primary, secondary, _ = call
        return (
            f"method:{primary}.{secondary}"
            if call_type == "method"
            else f"function:{primary}"
        )

    groups = []
    current_group = [calls[0]]

    for i in range(1, len(calls)):
        prev, curr = calls[i - 1], calls[i]
        same_target = prev[0] == curr[0] and _get_target(prev) == _get_target(curr)
        consecutive_lines = curr[3] - prev[3] <= 10

        if same_target and consecutive_lines:
            current_group.append(curr)
        else:
            if len(current_group) >= _MIN_CONSECUTIVE_CALLS:
                groups.append(current_group)
            current_group = [curr]

    if len(current_group) >= _MIN_CONSECUTIVE_CALLS:
        groups.append(current_group)

    return groups


def check_repeated_calls(root: Node) -> list[PatternViolation]:
    """Detect sequences of repeated calls that could be refactored to loops.

    Uses tree-sitter S-queries to find pairs of adjacent calls to:
    - The same method on the same object (e.g., parser.add_argument())
    - The same standalone function (e.g., print())

    Then post-processes to find longer chains and report violations.

    Args:
        root: AST root node

    Returns:
        List of PatternViolation objects for each group of repeated calls
    """
    queries = [_PY_REPEATED_CALLS_QUERY, _PY_REPEATED_FUNCTION_CALLS_QUERY]
    calls_by_line: dict[int, tuple[str, str, str | None, int]] = {}

    for query in queries:
        query_obj = create_query(PY_LANGUAGE, query)
        for node, capture_name in run_captures(query_obj, root):
            if capture_name in ("call1", "call2"):
                info = _get_call_info(node)
                if info:
                    calls_by_line[info[3]] = info

    sorted_calls = sorted(calls_by_line.values(), key=lambda x: x[3])
    groups = _find_consecutive_groups(sorted_calls)

    return [
        PatternViolation(
            type="repeated_calls",
            line=group[0][3],
            description=_build_violation_description(group),
            severity="info",
        )
        for group in groups
    ]


def _build_violation_description(group: list[tuple]) -> str:
    """Build violation description for a group of repeated calls."""
    first_call = group[0]
    call_type = first_call[0]
    target_name = (
        f"{first_call[1]}.{first_call[2]}" if call_type == "method" else first_call[1]
    )
    return f"{len(group)} consecutive calls to {target_name} could be refactored to a loop over a config/list/dict"
