"""Source information"""

import os
from dataclasses import dataclass, field
from pathlib import Path

import yaml

from glotter_core.project import CoreProjectMixin, NamingScheme
from glotter_core.testinfo import TestInfo


@dataclass(frozen=True)
class CoreSource:
    """Metadata about a source file

    :param filename: filename including extension
    :param language: the language of the source
    :param path: path to the file excluding name
    :param str test_info: a string in yaml format containing testinfo for a directory
    :param project_type: name of project for this source

    :ivar filename: filename including extension
    :ivar language: the language of the source
    :ivar path: path to the file excluding name
    :ivar TestInfo test_info: TestInfo object
    :param project_type: name of project for this source
    """

    filename: str
    language: str
    path: str
    test_info: str = field(repr=False)
    project_type: str

    def __post_init__(self) -> None:
        object.__setattr__(self, "test_info", TestInfo.from_string(self.test_info, self))

    @property
    def full_path(self) -> str:
        """Returns the full path to the source including filename and extension"""
        return str(Path(self.path) / self.filename)

    @property
    def name(self) -> str:
        """Returns the name of the source excluding the extension"""
        return self.filename.split(".")[0]

    @property
    def extension(self) -> str:
        """Returns the extension of the source"""
        return "".join(Path(self.filename).suffixes)


@dataclass
class CoreLanguage:
    """
    Information about a language

    :ivar sources: list of source objects
    :ivar test_info: TestInfo object
    :ivar path: Path to TestInfo object file
    """

    sources: list[CoreSource]
    test_info: TestInfo
    test_info_path: Path


@dataclass
class CoreSourceCategories:
    """
    Categories for sources

    :ivar testable_by_project: dictionary whose key is the project type and
        whose value is a list of testable source object
    :ivar by_language: dictionary whose key is the language and whose
        value is a CoreLanguage object
    :ivar bad_sources: list of filenames that do not belong to a project
    """

    testable_by_project: dict[str, list[CoreSource]] = field(default_factory=dict)
    by_language: dict[str, list[CoreLanguage]] = field(default_factory=dict)
    bad_sources: list[str] = field(default_factory=list)


_IGNORED_FILENAMES = {"untestable.yml", "testinfo.yml", "README.md"}


def categorize_sources(
    path: str, projects: dict[str, CoreProjectMixin], source_cls: type
) -> CoreSourceCategories:
    """
    Categorize sources

    :param path: path to source directory
    :param projects: dictionary whose key is a project type and whose value is a
        CoreProjectMixin object
    :param source_cls: source object class
    :return: CoreSourceCategories object containing information of the source
        categories
    """

    categories = CoreSourceCategories()
    categories.testable_by_project = {k: [] for k in projects}
    orig_path = Path(path).resolve()
    for root, _, files in os.walk(path):
        current_path = Path(root).resolve()
        test_info_string = ""
        test_info_filename = ""
        if "testinfo.yml" in files:
            test_info_filename = "testinfo.yml"
            test_info_string = Path(current_path, test_info_filename).read_text(encoding="utf-8")
        elif "untestable.yml" in files:
            test_info_filename = "untestable.yml"
            test_info_string = _convert_untestable_to_testinfo(current_path, files, projects)

        if test_info_string:
            language = current_path.name
            test_info = TestInfo.from_dict(yaml.safe_load(test_info_string), language)
            folder_info = test_info.file_info
            folder_project_names = folder_info.get_project_mappings(
                projects, include_extension=True
            )
            sources = []
            test_info_path = Path(current_path, test_info_filename)
            for project_type, project_name in folder_project_names.items():
                if project_name in files:
                    source = source_cls(
                        filename=project_name,
                        language=language,
                        path=str(current_path),
                        test_info=test_info_string,
                        project_type=project_type,
                    )
                    sources.append(source)
                    if source.test_info.is_testable:
                        categories.testable_by_project[project_type].append(source)

            categories.by_language[language] = CoreLanguage(sources, test_info, test_info_path)

            invalid_filenames = set(files) - (
                set(folder_project_names.values()) | _IGNORED_FILENAMES
            )
            categories.bad_sources += [
                str(current_path.relative_to(orig_path) / filename)
                for filename in invalid_filenames
            ]

    return categories


def _convert_untestable_to_testinfo(
    current_path: Path, files: list[str], projects: dict[str, CoreProjectMixin]
) -> str:
    with Path(current_path, "untestable.yml").open(encoding="utf-8") as f:
        untestable_data = yaml.safe_load(f)

    notes = untestable_data[0]["reason"]
    for filename in files:
        if filename in _IGNORED_FILENAMES:
            continue

        base_filename = filename.split(".")[0]
        extension = "".join(Path(filename).suffixes)
        project_type = base_filename.lower().replace("-", "").replace("_", "")
        if project_type in projects and len(projects[project_type].words) > 1:
            for naming_scheme in NamingScheme:
                expected_filename = (
                    projects[project_type].get_project_name_by_scheme(naming_scheme) + extension
                )
                if filename == expected_filename:
                    test_info_dict = {
                        "folder": {
                            "extension": extension,
                            "naming": naming_scheme.value,
                        },
                        "notes": [notes],
                    }
                    return yaml.dump(test_info_dict, sort_keys=False)

    return ""


__all__ = ["CoreLanguage", "CoreSource", "CoreSourceCategories", "categorize_sources"]
