import json
import logging
import os
from typing import (
    Dict,
    List,
    Optional,
    Union,
)

from cfgtools import read
from filesystem import FileSystem
from message_handler_builder import Configs
from nreports.utils import pickle_deep_copy
from nreports_api.shared import JSON
from systools import sys_parsefilepath
from ._IncludeCycle import IncludeCycle

logger = logging.getLogger(name=__name__)


class Configurator(object):

    def __init__(
        self,
        loader_path: str,
        file_system: FileSystem,
    ) -> None:

        self.loader_path = loader_path
        self.file_system = file_system

        self.reader = read(file_or_path=loader_path)
        self.group = Configs.component_name + "."
        self._default_images_path = os.path.join("..", "src", "nreports", "lib", "images")

    def get_configs(
        self,
    ) -> Configs:

        database_dir = os.path.join(os.path.dirname(self.loader_path), "..", "..", "data", "server", "databases")
        report_files = self._read_report_files()
        extra_modules = self.reader.find_values(path=self.group + "BuilderExtraModules") or []
        images_path = self._read_images_path()
        constants = self._read_constants()

        return Configs(
            database_dir=database_dir,
            report_files=report_files,
            images_path=images_path,
            constants=constants,
            builder_extra_modules=extra_modules,
        )

    def get_required_services(
        self,
    ) -> str:
        return "|".join(self.reader.find_values(path=self.group + "RequiredServices") or [])

    def _find(
        self,
        key: str,
        default_value: Optional[str] = None,
    ) -> str:

        return self.reader.find_value(
            path=self.group + key,
            default=default_value,
        )

    def _read_report_files(
        self,
    ) -> Dict[str, List[JSON]]:

        report_files_dirs = [os.path.join("..", "src", "nreports", "reports")]
        extra_report_files_dirs = self.reader.find_values(path=self.group + "ExtraReportFileDir")
        if extra_report_files_dirs is None:
            extra_report_files_dirs = []
        report_files_dirs.extend(extra_report_files_dirs)

        report_files_dirs = map(sys_parsefilepath, report_files_dirs)

        report_files = {}
        for report_files_dir in report_files_dirs:
            report_files.update(self._read_report_from_dir(report_files_dir=report_files_dir))

        return self._handle_include_for_each_report(report_files=report_files)

    def _read_report_from_dir(
        self,
        report_files_dir: str,
    ) -> Dict[str, JSON]:

        report_files = {}
        files = self.file_system.list_entries(path=report_files_dir)
        for report_file in files:
            if self.file_system.is_dir(report_file):
                report_files.update(self._read_report_from_dir(report_files_dir=report_file))
                continue

            base_name = os.path.basename(report_file)
            base_name = base_name.upper()

            report_name, extension = base_name.split(".")
            if extension != "JSON":
                continue

            file_content = self.file_system.read_file_content(path=report_file)
            report_files[report_name] = json.loads(file_content.decode())

        return report_files

    def _handle_include_for_each_report(
        self,
        report_files: Dict[str, List[JSON]],
    ) -> Dict[str, List[JSON]]:

        complete_report_files = {}
        for report_name, report_file in report_files.items():
            complete_report_files[report_name] = self._handle_include_line(
                report_name=report_name,
                report=report_file,
                report_files=report_files,
            )

        return complete_report_files

    def _handle_include_line(
        self,
        report_name: str,
        report: List[JSON],
        report_files: Dict[str, List[JSON]],
        already_processed: Optional[List[str]] = None,
        condition_to_include: Optional[Union[str, List[str]]] = None,
    ) -> List[JSON]:

        if already_processed is None:
            already_processed = []

        if report_name in already_processed:
            raise IncludeCycle(cycle=list(already_processed) + [report_name])

        complete_lines = []
        for report_line in report:
            if condition_to_include:
                report_line["condition"] = self._get_propagated_condition(
                    condition_to_include=condition_to_include,
                    report_line=report_line,
                )

            include_report_name = report_line.get("include")
            if include_report_name is None:
                complete_lines.append(report_line)
            else:
                include_report_name = include_report_name.upper()
                already_processed.append(report_name)
                report_to_include = self._handle_include_line(
                    report_name=include_report_name,
                    report=pickle_deep_copy(obj=report_files[include_report_name]),
                    report_files=report_files,
                    already_processed=already_processed,
                    condition_to_include=report_line.get("condition"),
                )
                already_processed = []
                complete_lines.extend(report_to_include)

        return complete_lines

    @staticmethod
    def _get_propagated_condition(
        condition_to_include: Union[str, List[str]],
        report_line: JSON,
    ) -> List[str]:

        condition = report_line.get("condition")

        if isinstance(condition_to_include, str):
            condition_to_include = [condition_to_include]

        if not condition:
            return condition_to_include

        if isinstance(report_line["condition"], str):
            condition = [condition]

        condition.extend(condition_to_include)

        return list(set(condition))

    def _read_images_path(
        self,
    ) -> Dict[str, str]:

        images_path = self.reader.find_value(
            path="nReports.ImagesPath",
            default=self._default_images_path,
        )
        if not self.file_system.is_dir(images_path):
            return {}

        images_by_path = {}
        for file in self.file_system.list_entries(images_path):
            if not self.file_system.is_file(file):
                continue

            file_name_no_extension, _ = os.path.splitext(os.path.basename(file))

            images_by_path[file_name_no_extension] = file

        return images_by_path

    def _read_constants(
        self,
    ) -> Dict[str, str]:

        constants_group = self.reader.find_group(path="nReports.Constants")

        constants: Dict[str, str] = {}
        for key in constants_group.keys:
            value = key.value()
            if not value:
                continue

            constants[key.name] = value

        return constants
