from abc import abstractmethod
from dataclasses import dataclass, field
import json
from typing import Any, Dict, List, Literal, Optional

import yaml
import sigma
from sigma.exceptions import SigmaConfigurationError

from sigma.processing.templates import TemplateBase


@dataclass
class Finalizer:
    """Conversion output transformation base class."""

    @classmethod
    def from_dict(cls, d: dict) -> "Finalizer":
        try:
            return cls(**d)
        except TypeError as e:
            raise SigmaConfigurationError("Error in instantiation of finalizer: " + str(e))

    @abstractmethod
    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[Any]
    ) -> Any:
        """Finalize output by applying a transformation to the list of generated and postprocessed queries.

        :param pipeline: Processing pipeline this transformation was contained.
        :type pipeline: sigma.processing.pipeline.ProcessingPipeline
        :param queries: List of converted and postprocessed queries that should be finalized.
        :type queries: List[Any]
        :return: Output that can be used in further processing of the conversion result.
        :rtype: Any
        """


@dataclass
class ConcatenateQueriesFinalizer(Finalizer):
    """Concatenate queries with a given separator and embed result within a prefix or suffix
    string."""

    separator: str = "\n"
    prefix: str = ""
    suffix: str = ""

    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[str]
    ) -> str:
        return self.prefix + self.separator.join(queries) + self.suffix


@dataclass
class JSONFinalizer(Finalizer):
    indent: Optional[int] = None

    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[Any]
    ) -> str:
        return json.dumps(queries, indent=self.indent)


@dataclass
class YAMLFinalizer(Finalizer):
    indent: Optional[int] = None

    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[Any]
    ) -> str:
        yaml.safe_dump(queries, indent=self.indent)


@dataclass
class TemplateFinalizer(Finalizer, TemplateBase):
    """Apply Jinja2 template provided as template object variable to the queries. The following
    variables are available in the context:

    * queries: all post-processed queries generated by the backend.
    * pipeline: the Sigma processing pipeline where this transformation is applied including all
      current state information in pipeline.state.

    if *path* is given, *template* is considered as a relative path to a template file below the
    specified path. If it is not provided, the template is specified as plain string. *autoescape*
    controls the Jinja2 HTML/XML auto-escaping.
    """

    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[Any]
    ) -> str:
        return self.j2template.render(queries=queries, pipeline=pipeline)


@dataclass
class NestedFinalizer(Finalizer):
    """Apply a list of finalizers to the queries in a nested fashion."""

    finalizers: List[Finalizer]
    _nested_pipeline: "sigma.processing.pipeline.ProcessingPipeline" = field(
        init=False, compare=False, default=None
    )

    def __post_init__(self):
        from sigma.processing.pipeline import (
            ProcessingPipeline,
        )  # TODO: move to top after restructuring code.

        self._nested_pipeline = ProcessingPipeline(finalizers=self.finalizers)

    @classmethod
    def from_dict(cls, d: Dict) -> "NestedFinalizer":
        if not "finalizers" in d:
            raise SigmaConfigurationError("Nested finalizer requires a 'finalizers' key.")
        fs = []
        for finalizer in d["finalizers"]:
            try:
                finalizer_type = finalizer.pop("type")
            except KeyError:
                raise SigmaConfigurationError("Finalizer type not specified for: " + str(finalizer))
            fs.append(finalizers[finalizer_type].from_dict(finalizer))
        return cls(finalizers=fs)

    def apply(
        self, pipeline: "sigma.processing.pipeline.ProcessingPipeline", queries: List[Any]
    ) -> Any:
        return self._nested_pipeline.finalize(queries)


finalizers: Dict[str, Finalizer] = {
    "concat": ConcatenateQueriesFinalizer,
    "json": JSONFinalizer,
    "yaml": YAMLFinalizer,
    "template": TemplateFinalizer,
    "nested": NestedFinalizer,
}
