from pathlib import Path
from typing import Any, Optional

from yanga.cmake.artifacts_locator import CMakeArtifactsLocator
from yanga.cmake.cmake_backend import CMakeCommand, CMakeComment, CMakeCustomTarget, CMakeElement, CMakePath
from yanga.cmake.generator import CMakeGenerator
from yanga.docs.sphinx import SphinxConfig
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 ReportCMakeGenerator(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)

    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] = []
        variant_report_dir = self.artifacts_locator.cmake_variant_reports_dir
        elements.append(
            CMakeCustomTarget(
                name=UserRequest(
                    UserRequestScope.VARIANT,
                    target=UserRequestTarget.REPORT,
                ).target_name,
                description=f"Run sphinx build for variant {self.execution_context.variant_name}",
                commands=[
                    CMakeCommand(
                        "${CMAKE_COMMAND}",
                        [
                            "-E",
                            "make_directory",
                            variant_report_dir,
                        ],
                    ),
                    CMakeCommand(
                        "${CMAKE_COMMAND}",
                        [
                            "-E",
                            "env",
                            f"{SphinxConfig.REPORT_CONFIGURATION_FILE_ENV_NAME}={self.artifacts_locator.variant_report_config}",
                            "--",
                            "sphinx-build",
                            "-E",
                            "-b",
                            "html",
                            self.artifacts_locator.cmake_project_dir,
                            variant_report_dir,
                        ],
                    ),
                ],
                depends=[self.artifacts_locator.variant_report_config],
            )
        )
        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())
            component_build_dir = self.artifacts_locator.get_component_build_dir(component.name)
            report_config_output_file = self.artifacts_locator.get_component_report_config(component.name)
            docs_source_files = [CMakePath(source) for source in component_analyzer.collect_docs_sources()]
            source_files: list[CMakePath] = [CMakePath(source) for source in [*component_analyzer.collect_sources(), *component_analyzer.collect_test_sources()]]
            source_files_output_md = [component_build_dir.joinpath(f"{source_file.to_path().name}.md") for source_file in source_files]
            component_docs_target = UserRequest(
                UserRequestScope.COMPONENT,
                target=UserRequestTarget.DOCS,
                component_name=component.name,
            )

            elements.append(
                CMakeCustomTarget(
                    name=component_docs_target.target_name,
                    description=f"Generate sources docs for component {component.name}",
                    commands=[
                        CMakeCommand(
                            "clanguru",
                            [
                                "generate",
                                "--source-file",
                                source_file,
                                "--output-file",
                                output_md,
                                "--compilation-database",
                                self.artifacts_locator.compile_commands_file,
                            ],
                        )
                        for source_file, output_md in zip(source_files, source_files_output_md)
                    ],
                    depends=[self.artifacts_locator.compile_commands_file],
                    byproducts=source_files_output_md,
                )
            )

            # Register the component documentation files as relevant for the component report
            self.execution_context.data_registry.insert(
                ReportRelevantFiles(
                    target=component_docs_target,
                    files_to_be_included=[source.to_path() for source in docs_source_files],
                    file_type=ReportRelevantFileType.DOCS,
                ),
                component_docs_target.target_name,
            )

            component_report_target = UserRequest(
                UserRequestScope.COMPONENT,
                target=UserRequestTarget.REPORT,
                component_name=component.name,
            )

            component_report_dir = self.artifacts_locator.get_component_reports_dir(component.name)
            elements.append(
                CMakeCustomTarget(
                    name=component_report_target.target_name,
                    description=f"Generate report for component {component.name}",
                    commands=[
                        CMakeCommand(
                            "${CMAKE_COMMAND}",
                            [
                                "-E",
                                "make_directory",
                                component_report_dir,
                            ],
                        ),
                        CMakeCommand(
                            "yanga_cmd",
                            [
                                "report_config",
                                "--component-name",
                                component.name,
                                "--output-file",
                                report_config_output_file,
                                "--variant-report-config",
                                self.artifacts_locator.variant_report_config,
                            ],
                        ),
                        CMakeCommand(
                            "${CMAKE_COMMAND}",
                            [
                                "-E",
                                "env",
                                f"REPORT_CONFIGURATION_FILE={report_config_output_file}",
                                "--",
                                "sphinx-build",
                                "-E",
                                "-b",
                                "html",
                                self.artifacts_locator.cmake_project_dir,
                                component_report_dir,
                            ],
                        ),
                    ],
                    depends=[
                        self.artifacts_locator.variant_report_config,
                        *source_files_output_md,
                        UserRequest(
                            UserRequestScope.COMPONENT,
                            target=UserRequestTarget.LINT,
                            component_name=component.name,
                        ).target_name,
                        UserRequest(
                            UserRequestScope.COMPONENT,
                            target=UserRequestTarget.COVERAGE,
                            component_name=component.name,
                        ).target_name,
                    ],
                )
            )
            # Register the component sources md files as relevant for the component report
            self.execution_context.data_registry.insert(
                ReportRelevantFiles(
                    target=component_report_target,
                    files_to_be_included=[md_output.to_path() for md_output in source_files_output_md],
                    file_type=ReportRelevantFileType.SOURCES,
                ),
                component_report_target.target_name,
            )
        return elements
