#
# Copyright 2025 Splunk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from os import walk, path
from os.path import sep
from typing import Any, Optional
from logging import Logger
import addonfactory_splunk_conf_parser_lib as conf_parser


# Some fields are dynamically generated just like `build` for app.conf
# so while comparing them we should remove them from the dict
STANZA_TO_REMOVE = {"app.conf": {"install": ["build"]}}


class CodeGeneratorDiffChecker:
    COMMON_FILES_MESSAGE_PART_1 = (
        "Below are the file(s) that are auto generated by the UCC framework. "
        "The below files can be removed from your repository:"
    )
    COMMON_FILES_MESSAGE_PART_2 = (
        "Please refer UCC framework documentation for the latest "
        "features that allows you to remove the above files."
    )
    DIFFERENT_STANZA_MESSAGE = (
        "Below are the stanza(s) that are not auto generated by the UCC framework. "
        "(Optionally), you can raise feature requests for UCC framework at "
        "'https://github.com/splunk/addonfactory-ucc-generator/issues/new/choose' "
        "with the output mentioned below."
    )

    DIFFERENT_FILES_MESSAGE = (
        "Below are the file(s) with the attributes of the stanza that are not auto generated by the UCC framework. "
        "(Optionally), you can raise feature requests for UCC framework at "
        "'https://github.com/splunk/addonfactory-ucc-generator/issues/new/choose' "
        "with the output mentioned below."
    )

    def __init__(self, src_dir: str, dst_dir: str) -> None:
        self.source_default_directory = f"{src_dir}/default"
        self.target_default_directory = f"{dst_dir}/default"
        self.different_files: dict[str, Any[dict[str, str], list[dict[str, str]]]] = {}
        self.common_files: dict[str, str] = {}
        self.same_stanza: dict[str, str] = {}
        self.diff_stanza: dict[str, str] = {}

    def deduce_gen_and_custom_content(
        self, logger: Logger, ignore_file_list: Optional[list[str]] = None
    ) -> None:
        """
        Deduce that the files have same content or different
        - For the same content, developer can remove it from the repository
        """
        if ignore_file_list is None:
            ignore_file_list = []
        # we add these two files as they are required to be present in source code
        # TODO: try to implement generation of these files from globalConfig
        ignore_file_list.extend(["app.manifest", "README.txt"])

        src_all_files: dict[str, str] = {}
        for root, _, files in walk(self.source_default_directory):
            for file in files:
                src_all_files[file] = sep.join([root, file])

        dest_all_files: dict[str, str] = {}
        for root, _, files in walk(self.target_default_directory):
            for file in files:
                dest_all_files[file] = sep.join([root, file])
        dest_all_files["default.meta"] = sep.join(
            [self.target_default_directory, "metadata"]
        )

        for file_name in dest_all_files.keys():
            if file_name in src_all_files.keys():
                if file_name in ignore_file_list:
                    continue
                if file_name.endswith(("app.conf", "restmap.conf", "web.conf")):
                    self._conf_file_diff_checker(
                        src_all_files[file_name], dest_all_files[file_name]
                    )

        self.print_files(logger)

    def _conf_file_diff_checker(self, src_file: str, target_file: str) -> None:
        """
        Find the difference between the source code and generated code for the
        conf files created in package/default directory
        """
        sparser = conf_parser.TABConfigParser()
        sparser.read(src_file)
        src_dict = sparser.item_dict()
        parser = conf_parser.TABConfigParser()
        parser.read(target_file)
        dst_dict = parser.item_dict()
        file_name = path.basename(src_file)

        # Remove dynamically generated fields before comparing
        if file_name in STANZA_TO_REMOVE.keys():
            for stanza in STANZA_TO_REMOVE[file_name].keys():
                if stanza in src_dict.keys():
                    for key_value in STANZA_TO_REMOVE[file_name][stanza]:
                        src_dict[stanza].pop(key_value, None)
                        dst_dict[stanza].pop(key_value, None)
        if src_dict == dst_dict:
            self.common_files[src_file] = src_file.split(sep=sep)[-1]
        else:
            for stanza, src_kv in src_dict.items():
                dst_kv = dst_dict.get(stanza)

                # Stanza not present in destination that is we do not generate this whole stanza.
                if dst_kv is None:
                    for key, val in src_kv.items():
                        self.diff_stanza[f"{src_file} :: {stanza}"] = src_file.split(
                            sep=sep
                        )[-1]
                    continue

                # Stanza present, check individual key differences
                for key, val in src_kv.items():
                    if key not in dst_kv:
                        self.different_files[f"{src_file}[{stanza}] :: {key}"] = {
                            "repository": val,
                            "output": "",
                        }
                # If stanza dicts are identical, mark them as same stanza
                if src_kv == dst_kv:
                    self.same_stanza[f"{src_file} :: {stanza}"] = src_file.split(
                        sep=sep
                    )[-1]

    def print_files(self, logger: Logger) -> None:
        """
        Print the common and different files in the console
        """
        messages: list[str] = []
        if self.common_files:
            messages.append("-" * 120)
            messages.append(self.COMMON_FILES_MESSAGE_PART_1)
            messages.extend(
                [f"{idx + 1}) {f}" for idx, f in enumerate(self.common_files.keys())]
            )
            messages.append(self.COMMON_FILES_MESSAGE_PART_2)
            messages.append("-" * 120)
            logger.warning("\n\n".join(messages))

        messages.clear()

        # TODO: uncomment the below code when UCC provide functionality of merging .conf files
        # if self.same_stanza:
        #     messages.append("-" * 120)
        #     messages.append(
        #         "Below are the stanzas that can be removed from your source directory as, UCC auto generates it"
        #     )
        #     messages.extend(
        #         [f"{idx + 1}) {f}" for idx, f in enumerate(self.same_stanza.keys())]
        #     )
        #     messages.append("-" * 120)
        #     logger.warning("\n\n".join(messages))
        # messages.clear()

        # if self.diff_stanza:
        #     messages.append("-" * 120)
        #     messages.append(self.DIFFERENT_STANZA_MESSAGE)
        #     messages.extend(
        #         [f"{idx + 1}) {f}" for idx, f in enumerate(self.diff_stanza.keys())]
        #     )
        #     messages.append("-" * 120)
        #     logger.warning("\n\n".join(messages))
        # messages.clear()

        if self.different_files:
            messages.append("+" * 120)
            messages.append(self.DIFFERENT_FILES_MESSAGE)
            file_count = 1
            for k, v in self.different_files.items():
                file_msg: str = ""
                file_msg = f"{file_count}) {k}"
                # v would be of instance of list when we scope XML file for comparison
                if isinstance(v, dict):
                    file_msg += f"\n\tSource: {v.get('repository')}, Generated: {v.get('output')}"
                messages.append(file_msg)
                file_count += 1
            messages.append("+" * 120)
            logger.warning("\n".join(messages))
        messages.clear()
