from __future__ import annotations

import ast


def extract_constraint_value(node: ast.expr) -> float | int | str | None:
  """Extract constraint value from AST node.

  Handles Ge[10], Le[100], Pattern[r"..."], etc.
  Returns the value inside the brackets, or None if not extractable.
  """
  # Handle subscript like Ge[10]
  if isinstance(node, ast.Subscript):
    slice_node = node.slice
    # Numeric constraints: Ge[10], Le[100.5]
    if isinstance(slice_node, ast.Constant):
      return slice_node.value  # type: ignore[return-value]
    # String constraints: Pattern[r"..."]
    if isinstance(slice_node, ast.Constant) and isinstance(slice_node.value, str):
      return slice_node.value
  return None


def extract_constraints_from_annotation(
  annotation: ast.Subscript,
) -> dict[str, float | int | str]:
  """Extract constraint values from Hyper[T, Ge[2], Le[100]] annotation.

  Returns dict like {"ge": 2, "le": 100}
  """
  constraints: dict[str, float | int | str] = {}
  slice_node = annotation.slice

  if not isinstance(slice_node, ast.Tuple):
    return constraints

  # Process constraint metadata (skip first element which is the type)
  for constraint_node in slice_node.elts[1:]:
    if not isinstance(constraint_node, ast.Subscript):
      continue
    if not isinstance(constraint_node.value, ast.Name):
      continue

    constraint_name = constraint_node.value.id
    constraint_value = extract_constraint_value(constraint_node)

    if constraint_value is None:
      continue

    constraint_map = {
      "Ge": "ge",
      "Gt": "gt",
      "Le": "le",
      "Lt": "lt",
      "MinLen": "min_length",
      "MaxLen": "max_length",
      "MultipleOf": "multiple_of",
      "Pattern": "pattern",
    }

    field_name = constraint_map.get(constraint_name)
    if field_name:
      constraints[field_name] = constraint_value

  return constraints


def validate_constraint_conflicts(
  constraints: dict[str, float | int | str], param_name: str, func_name: str
) -> None:
  """Validate that constraints don't conflict (AST version).

  Raises:
    ValueError: If constraints are contradictory
  """
  # Check numeric bound conflicts
  ge_val = constraints.get("ge")
  gt_val = constraints.get("gt")
  le_val = constraints.get("le")
  lt_val = constraints.get("lt")

  # Determine effective lower and upper bounds
  lower_bound: float | int | None = None
  lower_inclusive = True
  if ge_val is not None and isinstance(ge_val, (int, float)):
    lower_bound = ge_val
    lower_inclusive = True
  if (
    gt_val is not None
    and isinstance(gt_val, (int, float))
    and (lower_bound is None or gt_val >= lower_bound)
  ):
    lower_bound = gt_val
    lower_inclusive = False

  upper_bound: float | int | None = None
  upper_inclusive = True
  if le_val is not None and isinstance(le_val, (int, float)):
    upper_bound = le_val
    upper_inclusive = True
  if (
    lt_val is not None
    and isinstance(lt_val, (int, float))
    and (upper_bound is None or lt_val <= upper_bound)
  ):
    upper_bound = lt_val
    upper_inclusive = False

  # Check if bounds are impossible
  if lower_bound is not None and upper_bound is not None:
    if lower_bound > upper_bound:
      msg = (
        f"In function '{func_name}', parameter '{param_name}' has "
        f"contradictory constraints: lower bound ({lower_bound}) is "
        f"greater than upper bound ({upper_bound})"
      )
      raise ValueError(msg)
    if lower_bound == upper_bound and not (lower_inclusive and upper_inclusive):
      msg = (
        f"In function '{func_name}', parameter '{param_name}' has "
        f"contradictory constraints: no value can satisfy both strict "
        f"and non-strict bounds at {lower_bound}"
      )
      raise ValueError(msg)

  # Check length constraint conflicts
  min_len = constraints.get("min_length")
  max_len = constraints.get("max_length")

  if (
    min_len is not None
    and max_len is not None
    and isinstance(min_len, int)
    and isinstance(max_len, int)
    and min_len > max_len
  ):
    msg = (
      f"In function '{func_name}', parameter '{param_name}' has "
      f"contradictory constraints: min_length ({min_len}) is greater "
      f"than max_length ({max_len})"
    )
    raise ValueError(msg)
