import io
import json
import traceback
from dataclasses import dataclass, field
from enum import auto
from pathlib import Path

from py_app_dev.core.exceptions import UserNotificationException

from .config import BaseConfigJSONMixin, StringableEnum, stringable_enum_field_metadata
from .execution_context import UserRequest


class ReportRelevantFileType(StringableEnum):
    """Describes the type of files relevant for reports generation."""

    DOCS = auto()
    SOURCES = auto()
    TEST_RESULT = auto()
    LINT_RESULT = auto()
    COVERAGE_RESULT = auto()
    OTHER = auto()


@dataclass
class ReportRelevantFiles(BaseConfigJSONMixin):
    """Used to register files relevant for reports generation."""

    #: CMake target name that generates the files
    target: UserRequest
    #: Describe the files type (e.g., docs, component sources, etc.)
    file_type: ReportRelevantFileType = field(metadata=stringable_enum_field_metadata(ReportRelevantFileType))
    #: List of relevant files generated by the target
    files_to_be_included: list[Path]
    #: List of all relevant files generated by the target. Some files may be excluded from other files and need to be included manually.
    all_files: list[Path] = field(default_factory=list)


@dataclass
class ComponentReportConfig(BaseConfigJSONMixin):
    """Configuration for a single component to be used by the report generation tools (e.g., Sphinx)."""

    name: str
    files: list[ReportRelevantFiles]

    @property
    def docs_files(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.DOCS)

    @property
    def sources(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.SOURCES)

    @property
    def test_results(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.TEST_RESULT)

    @property
    def lint_results(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.LINT_RESULT)

    @property
    def coverage_results(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.COVERAGE_RESULT)

    @property
    def other_files(self) -> list[Path]:
        return self.get_files_of_type(ReportRelevantFileType.OTHER)

    @property
    def all_files(self) -> list[Path]:
        """
        Collection of all files relevant for the component report generation.

        One must make sure all files can be found by the documentation tool.
        """
        return [file for entry in self.files for file in entry.files_to_be_included]

    def get_files_of_type(self, file_type: ReportRelevantFileType) -> list[Path]:
        relevant_files: list[ReportRelevantFiles] = [file for file in self.files if file.file_type == file_type]
        return [file for entry in relevant_files for file in entry.files_to_be_included]


@dataclass
class ReportConfig(BaseConfigJSONMixin):
    """Configuration use by the report generation tools (e.g., Sphinx)."""

    variant: str
    platform: str
    project_dir: Path
    # Updated only for single component reports
    component_name: str | None = None
    components: list[ComponentReportConfig] = field(default_factory=list)

    @property
    def has_component_scope(self) -> bool:
        return self.component_name is not None

    @classmethod
    def from_json_file(cls, file_path: Path) -> "ReportConfig":
        try:
            result = cls.from_dict(json.loads(file_path.read_text()))
        except Exception as e:
            output = io.StringIO()
            traceback.print_exc(file=output)
            raise UserNotificationException(output.getvalue()) from e
        return result

    def collect_all_files(self) -> list[Path]:
        result = []
        for comp in self.components:
            result.extend(comp.all_files)
        # Make result unique and keep order
        return list(dict.fromkeys(result))
