import json
from collections.abc import Mapping, Sequence
from typing import Literal, NamedTuple

from dify_graph.file import FileAttribute, file_manager
from dify_graph.runtime import VariablePool
from dify_graph.variables import ArrayFileSegment
from dify_graph.variables.segments import ArrayBooleanSegment, BooleanSegment

from .entities import Condition, SubCondition, SupportedComparisonOperator


def _convert_to_bool(value: object) -> bool:
    if isinstance(value, int):
        return bool(value)

    if isinstance(value, str):
        loaded = json.loads(value)
        if isinstance(loaded, (int, bool)):
            return bool(loaded)

    raise TypeError(f"unexpected value: type={type(value)}, value={value}")


class ConditionCheckResult(NamedTuple):
    inputs: Sequence[Mapping[str, object]]
    group_results: Sequence[bool]
    final_result: bool


class ConditionProcessor:
    def process_conditions(
        self,
        *,
        variable_pool: VariablePool,
        conditions: Sequence[Condition],
        operator: Literal["and", "or"],
    ) -> ConditionCheckResult:
        input_conditions: list[Mapping[str, object]] = []
        group_results: list[bool] = []

        for condition in conditions:
            variable = variable_pool.get(condition.variable_selector)
            if variable is None:
                raise ValueError(f"Variable {condition.variable_selector} not found")

            if isinstance(variable, ArrayFileSegment) and condition.comparison_operator in {
                "contains",
                "not contains",
                "all of",
            }:
                # check sub conditions
                if not condition.sub_variable_condition:
                    raise ValueError("Sub variable is required")
                result = _process_sub_conditions(
                    variable=variable,
                    sub_conditions=condition.sub_variable_condition.conditions,
                    operator=condition.sub_variable_condition.logical_operator,
                )
            elif condition.comparison_operator in {
                "exists",
                "not exists",
            }:
                result = _evaluate_condition(
                    value=variable.value,
                    operator=condition.comparison_operator,
                    expected=None,
                )
            else:
                actual_value = variable.value if variable else None
                expected_value: str | Sequence[str] | bool | list[bool] | None = condition.value
                if isinstance(expected_value, str):
                    expected_value = variable_pool.convert_template(expected_value).text
                # Here we need to explicit convet the input string to boolean.
                if isinstance(variable, (BooleanSegment, ArrayBooleanSegment)) and expected_value is not None:
                    # The following two lines is for compatibility with existing workflows.
                    if isinstance(expected_value, list):
                        expected_value = [_convert_to_bool(i) for i in expected_value]
                    else:
                        expected_value = _convert_to_bool(expected_value)
                input_conditions.append(
                    {
                        "actual_value": actual_value,
                        "expected_value": expected_value,
                        "comparison_operator": condition.comparison_operator,
                    }
                )
                result = _evaluate_condition(
                    value=actual_value,
                    operator=condition.comparison_operator,
                    expected=expected_value,
                )
            group_results.append(result)
            # Implemented short-circuit evaluation for logical conditions
            if (operator == "and" and not result) or (operator == "or" and result):
                final_result = result
                return ConditionCheckResult(input_conditions, group_results, final_result)

        final_result = all(group_results) if operator == "and" else any(group_results)
        return ConditionCheckResult(input_conditions, group_results, final_result)


def _evaluate_condition(
    *,
    operator: SupportedComparisonOperator,
    value: object,
    expected: str | Sequence[str] | bool | Sequence[bool] | None,
) -> bool:
    match operator:
        case "contains":
            return _assert_contains(value=value, expected=expected)
        case "not contains":
            return _assert_not_contains(value=value, expected=expected)
        case "start with":
            return _assert_start_with(value=value, expected=expected)
        case "end with":
            return _assert_end_with(value=value, expected=expected)
        case "is":
            return _assert_is(value=value, expected=expected)
        case "is not":
            return _assert_is_not(value=value, expected=expected)
        case "empty":
            return _assert_empty(value=value)
        case "not empty":
            return _assert_not_empty(value=value)
        case "=":
            return _assert_equal(value=value, expected=expected)
        case "≠":
            return _assert_not_equal(value=value, expected=expected)
        case ">":
            return _assert_greater_than(value=value, expected=expected)
        case "<":
            return _assert_less_than(value=value, expected=expected)
        case "≥":
            return _assert_greater_than_or_equal(value=value, expected=expected)
        case "≤":
            return _assert_less_than_or_equal(value=value, expected=expected)
        case "null":
            return _assert_null(value=value)
        case "not null":
            return _assert_not_null(value=value)
        case "in":
            return _assert_in(value=value, expected=expected)
        case "not in":
            return _assert_not_in(value=value, expected=expected)
        case "all of" if isinstance(expected, list):
            # Type narrowing: at this point expected is a list, could be list[str] or list[bool]
            if all(isinstance(item, str) for item in expected):
                # Create a new typed list to satisfy type checker
                str_list: list[str] = [item for item in expected if isinstance(item, str)]
                return _assert_all_of(value=value, expected=str_list)
            elif all(isinstance(item, bool) for item in expected):
                # Create a new typed list to satisfy type checker
                bool_list: list[bool] = [item for item in expected if isinstance(item, bool)]
                return _assert_all_of_bool(value=value, expected=bool_list)
            else:
                raise ValueError("all of operator expects homogeneous list of strings or booleans")
        case "exists":
            return _assert_exists(value=value)
        case "not exists":
            return _assert_not_exists(value=value)
        case _:
            raise ValueError(f"Unsupported operator: {operator}")


def _assert_contains(*, value: object, expected: object) -> bool:
    if not value:
        return False

    if not isinstance(value, (str, list)):
        raise ValueError("Invalid actual value type: string or array")

    # Type checking ensures value is str or list at this point
    if isinstance(value, str):
        if not isinstance(expected, str):
            expected = str(expected)
        if expected not in value:
            return False
    else:  # value is list
        if expected not in value:
            return False
    return True


def _assert_not_contains(*, value: object, expected: object) -> bool:
    if not value:
        return True

    if not isinstance(value, (str, list)):
        raise ValueError("Invalid actual value type: string or array")

    # Type checking ensures value is str or list at this point
    if isinstance(value, str):
        if not isinstance(expected, str):
            expected = str(expected)
        if expected in value:
            return False
    else:  # value is list
        if expected in value:
            return False
    return True


def _assert_start_with(*, value: object, expected: object) -> bool:
    if not value:
        return False

    if not isinstance(value, str):
        raise ValueError("Invalid actual value type: string")

    if not isinstance(expected, str):
        raise ValueError("Expected value must be a string for startswith")
    if not value.startswith(expected):
        return False
    return True


def _assert_end_with(*, value: object, expected: object) -> bool:
    if not value:
        return False

    if not isinstance(value, str):
        raise ValueError("Invalid actual value type: string")

    if not isinstance(expected, str):
        raise ValueError("Expected value must be a string for endswith")
    if not value.endswith(expected):
        return False
    return True


def _assert_is(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (str, bool)):
        raise ValueError("Invalid actual value type: string or boolean")

    if value != expected:
        return False
    return True


def _assert_is_not(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (str, bool)):
        raise ValueError("Invalid actual value type: string or boolean")

    if value == expected:
        return False
    return True


def _assert_empty(*, value: object) -> bool:
    if not value:
        return True
    return False


def _assert_not_empty(*, value: object) -> bool:
    if value:
        return True
    return False


def _normalize_numeric_values(value: int | float, expected: object) -> tuple[int | float, int | float]:
    """
    Normalize value and expected to compatible numeric types for comparison.

    Args:
        value: The actual numeric value (int or float)
        expected: The expected value (int, float, or str)

    Returns:
        A tuple of (normalized_value, normalized_expected) with compatible types

    Raises:
        ValueError: If expected cannot be converted to a number
    """
    if not isinstance(expected, (int, float, str)):
        raise ValueError(f"Cannot convert {type(expected)} to number")

    # Convert expected to appropriate numeric type
    if isinstance(expected, str):
        # Try to convert to float first to handle decimal strings
        try:
            expected_float = float(expected)
        except ValueError as e:
            raise ValueError(f"Cannot convert '{expected}' to number") from e

        # If value is int and expected is a whole number, keep as int comparison
        if isinstance(value, int) and expected_float.is_integer():
            return value, int(expected_float)
        else:
            # Otherwise convert value to float for comparison
            return float(value) if isinstance(value, int) else value, expected_float
    elif isinstance(expected, float):
        # If expected is already float, convert int value to float
        return float(value) if isinstance(value, int) else value, expected
    else:
        # expected is int
        return value, expected


def _assert_equal(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float, bool)):
        raise ValueError("Invalid actual value type: number or boolean")

    # Handle boolean comparison
    if isinstance(value, bool):
        if not isinstance(expected, (bool, int, str)):
            raise ValueError(f"Cannot convert {type(expected)} to bool")
        expected = bool(expected)
    elif isinstance(value, int):
        if not isinstance(expected, (int, float, str)):
            raise ValueError(f"Cannot convert {type(expected)} to int")
        expected = int(expected)
    else:
        if not isinstance(expected, (int, float, str)):
            raise ValueError(f"Cannot convert {type(expected)} to float")
        expected = float(expected)

    if value != expected:
        return False
    return True


def _assert_not_equal(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float, bool)):
        raise ValueError("Invalid actual value type: number or boolean")

    # Handle boolean comparison
    if isinstance(value, bool):
        if not isinstance(expected, (bool, int, str)):
            raise ValueError(f"Cannot convert {type(expected)} to bool")
        expected = bool(expected)
    elif isinstance(value, int):
        if not isinstance(expected, (int, float, str)):
            raise ValueError(f"Cannot convert {type(expected)} to int")
        expected = int(expected)
    else:
        if not isinstance(expected, (int, float, str)):
            raise ValueError(f"Cannot convert {type(expected)} to float")
        expected = float(expected)

    if value == expected:
        return False
    return True


def _assert_greater_than(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float)):
        raise ValueError("Invalid actual value type: number")

    value, expected = _normalize_numeric_values(value, expected)
    return value > expected


def _assert_less_than(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float)):
        raise ValueError("Invalid actual value type: number")

    value, expected = _normalize_numeric_values(value, expected)
    return value < expected


def _assert_greater_than_or_equal(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float)):
        raise ValueError("Invalid actual value type: number")

    value, expected = _normalize_numeric_values(value, expected)
    return value >= expected


def _assert_less_than_or_equal(*, value: object, expected: object) -> bool:
    if value is None:
        return False

    if not isinstance(value, (int, float)):
        raise ValueError("Invalid actual value type: number")

    value, expected = _normalize_numeric_values(value, expected)
    return value <= expected


def _assert_null(*, value: object) -> bool:
    if value is None:
        return True
    return False


def _assert_not_null(*, value: object) -> bool:
    if value is not None:
        return True
    return False


def _assert_in(*, value: object, expected: object) -> bool:
    if not value:
        return False

    if not isinstance(expected, list):
        raise ValueError("Invalid expected value type: array")

    if value not in expected:
        return False
    return True


def _assert_not_in(*, value: object, expected: object) -> bool:
    if not value:
        return True

    if not isinstance(expected, list):
        raise ValueError("Invalid expected value type: array")

    if value in expected:
        return False
    return True


def _assert_all_of(*, value: object, expected: Sequence[str]) -> bool:
    if not value:
        return False

    # Ensure value is a container that supports 'in' operator
    if not isinstance(value, (list, tuple, set, str)):
        return False

    return all(item in value for item in expected)


def _assert_all_of_bool(*, value: object, expected: Sequence[bool]) -> bool:
    if not value:
        return False

    # Ensure value is a container that supports 'in' operator
    if not isinstance(value, (list, tuple, set)):
        return False

    return all(item in value for item in expected)


def _assert_exists(*, value: object) -> bool:
    return value is not None


def _assert_not_exists(*, value: object) -> bool:
    return value is None


def _process_sub_conditions(
    variable: ArrayFileSegment,
    sub_conditions: Sequence[SubCondition],
    operator: Literal["and", "or"],
) -> bool:
    files = variable.value
    group_results: list[bool] = []
    for condition in sub_conditions:
        key = FileAttribute(condition.key)
        values = [file_manager.get_attr(file=file, attr=key) for file in files]
        expected_value = condition.value
        if key == FileAttribute.EXTENSION:
            if not isinstance(expected_value, str):
                raise TypeError("Expected value must be a string when key is FileAttribute.EXTENSION")
            if expected_value and not expected_value.startswith("."):
                expected_value = "." + expected_value

            normalized_values: list[object] = []
            for value in values:
                if value and isinstance(value, str):
                    if not value.startswith("."):
                        value = "." + value
                normalized_values.append(value)
            values = normalized_values
        sub_group_results: list[bool] = [
            _evaluate_condition(
                value=value,
                operator=condition.comparison_operator,
                expected=expected_value,
            )
            for value in values
        ]
        # Determine the result based on the presence of "not" in the comparison operator
        result = all(sub_group_results) if "not" in condition.comparison_operator else any(sub_group_results)
        group_results.append(result)
    return all(group_results) if operator == "and" else any(group_results)
