from pathlib import Path
from typing import Any, Optional

from yanga.cmake.artifacts_locator import BuildArtifact, CMakeArtifactsLocator
from yanga.cmake.cmake_backend import CMakeCommand, CMakeComment, CMakeCustomCommand, CMakeCustomTarget, CMakeElement, CMakePath
from yanga.cmake.generator import CMakeGenerator
from yanga.domain.component_analyzer import ComponentAnalyzer
from yanga.domain.execution_context import ExecutionContext, UserRequest, UserRequestScope, UserRequestTarget
from yanga.domain.reports import ReportRelevantFiles, ReportRelevantFileType


class CppCheckCMakeGenerator(CMakeGenerator):
    def __init__(
        self,
        execution_context: ExecutionContext,
        output_dir: Path,
        config: Optional[dict[str, Any]] = None,
    ) -> None:
        super().__init__(execution_context, output_dir, config)
        self.artifacts_locator = CMakeArtifactsLocator(output_dir, execution_context.create_artifacts_locator())

    def generate(self) -> list[CMakeElement]:
        elements: list[CMakeElement] = []
        elements.append(CMakeComment(f"Generated by {self.__class__.__name__}"))
        elements.extend(self.create_variant_cmake_elements())
        elements.extend(self.create_components_cmake_elements())
        return elements

    def create_variant_cmake_elements(self) -> list[CMakeElement]:
        elements: list[CMakeElement] = []
        compile_commands_file = self.artifacts_locator.get_build_artifact(BuildArtifact.COMPILE_COMMANDS)
        xml_report_file = self.artifacts_locator.cmake_build_dir.joinpath("cppcheck_report.xml")
        md_report_file = self.artifacts_locator.cmake_build_dir.joinpath("cppcheck_report.md")
        compile_filter_command = CMakeCustomCommand(
            description="Run cppcheck for all sources",
            outputs=[xml_report_file, md_report_file],
            depends=[],
            commands=[
                CMakeCommand(
                    "cppcheck",
                    [
                        "--enable=all",
                        "--inconclusive",
                        "--std=c11",
                        "--language=c",
                        "--project=" + str(compile_commands_file),
                        "--xml",
                        "2> " + str(xml_report_file),
                    ],
                ),
                CMakeCommand(
                    "yanga_cmd",
                    [
                        "cppcheck_report",
                        "--input-file",
                        xml_report_file,
                        "--output-file",
                        md_report_file,
                        "--project-dir",
                        CMakePath(self.execution_context.project_root_dir),
                    ],
                ),
            ],
        )
        elements.append(compile_filter_command)
        # Add custom target for linting the component
        elements.append(
            CMakeCustomTarget(
                UserRequest(
                    UserRequestScope.VARIANT,
                    target=UserRequestTarget.LINT,
                ).target_name,
                "Lint the entire variant",
                [],
                compile_filter_command.outputs,
            )
        )
        return elements

    def create_components_cmake_elements(self) -> list[CMakeElement]:
        elements: list[CMakeElement] = []
        for component in self.execution_context.components:
            component_analyzer = ComponentAnalyzer([component], self.execution_context.create_artifacts_locator())
            sources = component_analyzer.collect_sources()
            component_compile_commands_file = self.artifacts_locator.get_component_build_artifact(component.name, BuildArtifact.COMPILE_COMMANDS)
            xml_report_file = self.artifacts_locator.get_component_build_dir(component.name).joinpath("cppcheck_report.xml")
            md_report_file = self.artifacts_locator.get_component_build_dir(component.name).joinpath("cppcheck_report.md")

            # TODO: Make sure the cpp commands depends on the component objects being built
            compile_filter_command = CMakeCustomCommand(
                description=f"Run cppcheck for component {component.name}",
                outputs=[component_compile_commands_file, xml_report_file, md_report_file],
                depends=[],
                commands=[
                    CMakeCommand(
                        "yanga_cmd",
                        [
                            "filter_compile_commands",
                            "--compilation-database",
                            self.artifacts_locator.get_build_artifact(BuildArtifact.COMPILE_COMMANDS),
                            "--source-files",
                            *[CMakePath(src) for src in sources],
                            "--output-file",
                            component_compile_commands_file,
                        ],
                    ),
                    CMakeCommand(
                        "cppcheck",
                        [
                            "--enable=all",
                            "--inconclusive",
                            "--std=c11",
                            "--language=c",
                            "--project=" + str(component_compile_commands_file),
                            "--xml",
                            "2> " + str(xml_report_file),
                        ],
                    ),
                    CMakeCommand(
                        "yanga_cmd",
                        [
                            "cppcheck_report",
                            "--input-file",
                            xml_report_file,
                            "--output-file",
                            md_report_file,
                            "--project-dir",
                            CMakePath(self.execution_context.project_root_dir),
                        ],
                    ),
                ],
            )
            elements.append(compile_filter_command)
            # Add custom target for linting the component
            component_lint_target = UserRequest(
                UserRequestScope.COMPONENT,
                component_name=component.name,
                target=UserRequestTarget.LINT,
            )

            elements.append(
                CMakeCustomTarget(
                    component_lint_target.target_name,
                    f"Lint the {component.name} component",
                    [],
                    compile_filter_command.outputs,
                )
            )
            # Register the component lint md report as relevant for the component report
            self.execution_context.data_registry.insert(
                ReportRelevantFiles(
                    target=component_lint_target,
                    files_to_be_included=[
                        md_report_file.to_path(),
                    ],
                    file_type=ReportRelevantFileType.LINT_RESULT,
                ),
                component_lint_target.target_name,
            )

        return elements
