from sigma.conversion.deferred import DeferredQueryExpression
from sigma.conversion.state import ConversionState
from sigma.rule import SigmaRule
from sigma.conversion.base import TextQueryBackend
from sigma.conditions import ConditionItem, ConditionAND, ConditionOR, ConditionNOT, \
    ConditionFieldEqualsValueExpression, ConditionValueExpression
from sigma.types import SigmaCompareExpression, SigmaString
import sigma
import re
from typing import ClassVar, Dict, Tuple, Pattern, List, Any, Optional, Union


class stixBackend(TextQueryBackend):
    """stix backend."""
    # See the pySigma documentation for further infromation:
    # https://sigmahq-pysigma.readthedocs.io/en/latest/Backends.html

    # Operator precedence: tuple of Condition{AND,OR,NOT} in order of precedence.
    # The backend generates grouping if required
    name: ClassVar[str] = "stix backend"
    formats: Dict[str, str] = {
        "default": "Plain stix queries",
        "stix": "'stix' output format",
    }

    precedence: ClassVar[Tuple[ConditionItem, ConditionItem, ConditionItem]] = (ConditionNOT, ConditionAND, ConditionOR)
    group_expression: ClassVar[
        str] = "({expr})"  # Expression for precedence override grouping as format string with {expr} placeholder

    # Generated query tokens
    token_separator: str = " "  # separator inserted between all boolean operators
    or_token: ClassVar[str] = "OR"
    and_token: ClassVar[str] = "AND"
    not_token: ClassVar[str] = "NOT"
    eq_token: ClassVar[str] = " = "
    not_eq_token: ClassVar[str] = " != "

    # String output
    ## Fields
    ### Quoting
    field_quote_pattern: ClassVar[Pattern] = re.compile(
        "^\\w+$")  # Quote field names if this pattern (doesn't) matches, depending on field_quote_pattern_negation. Field name is always quoted if pattern is not set.
    field_quote_pattern_negation: ClassVar[
        bool] = True  # Negate field_quote_pattern result. Field name is quoted if pattern doesn't matches if set to True (default).

    ### Escaping
    field_escape: ClassVar[str] = "\\"  # Character to escape particular parts defined in field_escape_pattern.
    field_escape_quote: ClassVar[bool] = True  # Escape quote string defined in field_quote
    field_escape_pattern: ClassVar[Pattern] = re.compile(
        "\\s")  # All matches of this pattern are prepended with the string contained in field_escape.

    ## Values
    str_quote: ClassVar[str] = "'"  # string quoting character (added as escaping character)
    escape_char: ClassVar[str] = "\\"  # Escaping character for special characrers inside string
    wildcard_multi: ClassVar[str] = "*"  # Character used as multi-character wildcard
    wildcard_single: ClassVar[str] = "*"  # Character used as single-character wildcard
    add_escaped: ClassVar[str] = "\\"  # Characters quoted in addition to wildcards and string quote
    filter_chars: ClassVar[str] = ""  # Characters filtered
    bool_values: ClassVar[Dict[bool, str]] = {  # Values to which boolean values are mapped.
        True: "true",
        False: "false",
    }
    like_token = "LIKE"

    # Regular expressions
    re_expression: ClassVar[
        str] = "{field} MATCHES '{regex}'"  # Regular expression query as format string with placeholders {field} and {regex}
    re_not_expression: ClassVar[str] = "{field} NOT MATCHES '{regex}'"
    re_escape_char: ClassVar[str] = "\\"  # Character used for escaping in regular expressions
    re_escape: ClassVar[Tuple[str]] = ()  # List of strings that are escaped

    # Numeric comparison operators
    compare_op_expression: ClassVar[
        str] = "{field} {operator} {value}"  # Compare operation query as format string with placeholders {field}, {operator} and {value}
    # Mapping between CompareOperators elements and strings used as replacement for {operator} in compare_op_expression
    compare_operators: ClassVar[Dict[SigmaCompareExpression.CompareOperators, str]] = {
        SigmaCompareExpression.CompareOperators.LT: "<",
        SigmaCompareExpression.CompareOperators.LTE: "<=",
        SigmaCompareExpression.CompareOperators.GT: ">",
        SigmaCompareExpression.CompareOperators.GTE: ">=",
    }

    # Null/None expressions
    field_null_expression: ClassVar[
        str] = "{field} is null"  # Expression for field has null value as format string with {field} placeholder for field name

    # Field value in list, e.g. "field in (value list)" or "field containsall (value list)"
    convert_or_as_in: ClassVar[bool] = False  # Convert OR as in-expression
    convert_and_as_in: ClassVar[bool] = False  # Convert AND as in-expression
    in_expressions_allow_wildcards: ClassVar[
        bool] = True  # Values in list can contain wildcards. If set to False (default) only plain values are converted into in-expressions.
    field_in_list_expression: ClassVar[
        str] = "{field} {op} ({list})"  # Expression for field in list of values as format string with placeholders {field}, {op} and {list}
    or_in_operator: ClassVar[
        str] = "in"  # Operator used to convert OR into in-expressions. Must be set if convert_or_as_in is set
    and_in_operator: ClassVar[
        str] = "contains-all"  # Operator used to convert AND into in-expressions. Must be set if convert_and_as_in is set
    list_separator: ClassVar[str] = ", "  # List element separator

    # Query finalization: appending and concatenating deferred query part
    deferred_start: ClassVar[str] = ""  # String used as separator between main query and deferred parts
    deferred_separator: ClassVar[str] = ""  # String used to join multiple deferred query parts
    deferred_only_query: ClassVar[str] = ""  # String used as query if final query only contains deferred expression

    # Documentation: https://sigmahq-pysigma.readthedocs.io/en/latest/Backends.html

    def __init__(self, processing_pipeline: Optional["sigma.processing.pipeline.ProcessingPipeline"] = None,
                 collect_errors: bool = False, **kwargs):
        super().__init__(processing_pipeline, collect_errors, **kwargs)

    @staticmethod
    def _toggle_within_not(state: ConversionState):
        state.processing_state['within_not'] = not (state.processing_state['within_not']) \
            if 'within_not' in state.processing_state else True

    @staticmethod
    def _check_is_field_valid(field: str):
        """Validate field name."""
        if ":" not in field:
            raise NotImplementedError("Backend does not support rules with unmapped values")

    @staticmethod
    def _escape_str_value(value: str) -> str:
        """Escape value string."""
        return value.replace("\\", "\\\\").replace("'", "\\'")

    def _convert_condition_and_or_token(self, and_or_token: str,
                                        state: ConversionState,
                                        cond: Union[ConditionAND, ConditionOR]) -> Union[str, DeferredQueryExpression]:
        if self.token_separator == and_or_token:
            joiner = and_or_token  # don't repeat the same thing triple times if separator equals and token
        else:
            joiner = self.token_separator + and_or_token + self.token_separator

        return joiner.join((
            converted
            for converted in (
                self.convert_condition_group(arg, state)
                for arg in cond.args
            )
            if converted is not None and not isinstance(converted, DeferredQueryExpression)
        ))

    def convert_condition_and(self, cond: ConditionAND, state: ConversionState) -> Union[str, DeferredQueryExpression]:
        """Conversion of AND conditions."""
        within_not = state.processing_state.get("within_not", False)
        and_token = self.and_token if not within_not else self.or_token
        try:
            return self._convert_condition_and_or_token(and_token, state, cond)
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Operator 'and' not supported by the backend")

    def convert_condition_or(self, cond: ConditionOR, state: ConversionState) -> Union[str, DeferredQueryExpression]:
        """Conversion of OR conditions."""
        within_not = state.processing_state.get("within_not", False)
        or_token = self.or_token if not within_not else self.and_token
        try:
            return self._convert_condition_and_or_token(or_token, state, cond)
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Operator 'or' not supported by the backend")

    def convert_condition_not(self, cond: ConditionNOT, state: ConversionState) -> Union[str, DeferredQueryExpression]:
        """
        Conversion of NOT conditions.
        Usually this is done by prefixing the condition with the NOT token, however, STIX syntax does not support this.
        Therefore, we need to invert the condition and wrap it in parentheses. This is done by toggling the within_not
        state and then converting the condition. Afterwards, the state is toggled back. This is done recursively for
        nested NOT conditions. The state is stored in the processing_state dictionary of the ConversionState object. If
        the state is not present, it is assumed to be False. The state is toggled by calling the _toggle_within_not
        method.
        """
        arg = cond.args[0]
        self._toggle_within_not(state)
        try:
            if arg.__class__ in self.precedence:  # group if AND or OR condition is negated
                expr = self.convert_condition_group(arg, state)
                self._toggle_within_not(state)
                return expr
            else:
                expr = self.convert_condition(arg, state)
                if isinstance(expr, DeferredQueryExpression):  # negate deferred expression and pass it to parent
                    raise NotImplementedError("Deferred expressions are not supported by the backend")
                else:  # convert negated expression to string
                    self._toggle_within_not(state)
                    return expr
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Operator 'not' not supported by the backend")

    def convert_condition_field_eq_val_str(self, cond: ConditionFieldEqualsValueExpression, state: ConversionState) -> \
            Union[str, DeferredQueryExpression]:
        self._check_is_field_valid(cond.field)
        within_not = state.processing_state.get("within_not", False)
        eq_token = self.not_eq_token if within_not else self.eq_token
        like_token = f'{self.not_token} {self.like_token}' if within_not else self.like_token
        try:
            field = cond.field
            val = self._escape_str_value(cond.value.to_plain())
            val_no_wc = val.rstrip(self.wildcard_multi).lstrip(self.wildcard_multi)
            # contains case
            if val.startswith(self.wildcard_single) and val.endswith(self.wildcard_single):
                result = field + self.token_separator + like_token + self.token_separator + \
                         self.str_quote + f'%{val_no_wc}%' + self.str_quote
            # startswith case
            elif val.endswith(self.wildcard_single) and not val.startswith(self.wildcard_single):
                result = field + self.token_separator + like_token + self.token_separator + \
                         self.str_quote + f'{val_no_wc}%' + self.str_quote
            # endswith case
            elif val.startswith(self.wildcard_single) and not val.endswith(self.wildcard_single):
                result = field + self.token_separator + like_token + self.token_separator + \
                         self.str_quote + f'%{val_no_wc}' + self.str_quote
            # plain equals case
            else:
                result = field + eq_token + self.str_quote + val + self.str_quote
            return result
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Field equals string value expressions are not supported by the backend")

    def convert_condition_field_eq_val_num(self, cond: ConditionFieldEqualsValueExpression, state: ConversionState) -> \
            Union[str, DeferredQueryExpression]:
        """Conversion of field = number value expressions"""
        self._check_is_field_valid(cond.field)
        within_not = state.processing_state.get("within_not", False)
        try:
            if within_not:
                return self.escape_and_quote_field(cond.field) + self.not_eq_token + str(cond.value)
            return self.escape_and_quote_field(cond.field) + self.eq_token + str(cond.value)
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Field equals numeric value expressions are not supported by the backend.")

    def convert_condition_field_eq_val_bool(self, cond: ConditionFieldEqualsValueExpression,
                                            state: ConversionState) -> Union[str, DeferredQueryExpression]:
        """Conversion of field = bool value expressions"""
        self._check_is_field_valid(cond.field)
        cond.value = SigmaString(str(cond.value))
        return self.convert_condition_field_eq_val_str(cond, state)

    def convert_condition_field_eq_val_re(self, cond: ConditionFieldEqualsValueExpression,
                                          state: ConversionState) -> Union[str, DeferredQueryExpression]:
        """Conversion of field matches regular expression value expressions."""
        self._check_is_field_valid(cond.field)
        within_not = state.processing_state.get("within_not", False)
        try:
            field = cond.field
            value = self._escape_str_value(cond.value.regexp)
            if within_not:
                return self.re_not_expression.format(
                    field=field,
                    regex=value
                )
            else:
                return self.re_expression.format(
                    field=field,
                    regex=value
                )
        except TypeError:  # pragma: no cover
            raise NotImplementedError("Reg expressions are not supported by the backend.")

    def convert_condition_field_eq_val_null(self, cond: ConditionFieldEqualsValueExpression, state: ConversionState) \
            -> Union[str, DeferredQueryExpression]:
        """Conversion of field is null expression value expressions"""
        raise NotImplementedError("Null expressions are not supported by the backend.")

    def convert_condition_val_str(self, cond: ConditionValueExpression, state: ConversionState) -> Union[
        str, DeferredQueryExpression]:
        """Conversion of value-only strings."""
        raise NotImplementedError("Value-only strings are not supported by the backend.")

    def convert_condition_val_num(self, cond: ConditionValueExpression, state: ConversionState) -> Union[
        str, DeferredQueryExpression]:
        """Conversion of value-only numbers."""
        raise NotImplementedError("Value-only strings are not supported by the backend.")

    def convert_condition_val_re(self, cond: ConditionValueExpression, state: ConversionState) -> Union[
        str, DeferredQueryExpression]:
        """Conversion of value-only regular expressions."""
        raise NotImplementedError("Value-only strings are not supported by the backend.")

    def finalize_query_default(self, rule: SigmaRule, query: Any, index: int, state: ConversionState) -> Any:
        return f'[{query}]'

    def finalize_output_stix(self, queries: List[str]) -> Any:
        return "\n".join(queries)
