"""Project Card class for project card data schema."""

from typing import List, Union

from .logger import CardLogger
from .utils import _findkeys
from .validate import (
    ProjectCardValidationError,
    SubprojectValidationError,
    validate_card,
)

UNSPECIFIED_PROJECT_NAMES = ["", "TO DO User Define", "USER TO define"]
VALID_EXT = [".yml", ".yaml", ".json", ".toml", ".wr", ".wrangler"]
REPLACE_KEYS = {"a": "A", "b": "B"}


class ProjectCard(object):
    """Representation of a Project Card.

    Attributes:
        __dict__: Dictionary of project card attributes
        valid: Boolean indicating if data conforms to project card data schema
        facilities: List of all facility objects in project card
        facility: either singular facility in project card or the string "multiple"
        all_property_changes: List of all property_changes objects in project card
        property_changes: either singular property_changes in project card or the string "multiple"
        change_types: List of all project types in project card
        change_type: either singular project type in project card or the string "multiple"
        sub_projects: list of sub_project objects
    """

    def __init__(self, attribute_dictonary: dict):
        """Constructor for ProjectCard object.

        Args:
            attribute_dictonary: a nested dictionary of attributes
        """
        # add these first so they are first on write out
        self.project: str = None
        self.tags: list[str] = []
        self.dependencies: dict = {}
        self.sub_projects: list[SubProject] = []

        self.__dict__.update(attribute_dictonary)
        for sp in self.__dict__.get("changes", []):
            sp_obj = SubProject(sp, self)
            self.sub_projects.append(sp_obj)

    def __str__(self):
        """String representation of project card."""
        s = ["{}: {}".format(key, value) for key, value in self.__dict__.items()]
        return "\n".join(s)

    def validate(self) -> bool:
        """Return True if project card is valid, False otherwise."""
        return validate_card(self.__dict__)

    @property
    def valid(self) -> bool:
        """Return True if project card is valid, False otherwise."""
        try:
            self.validate()
        except ProjectCardValidationError as e:
            CardLogger.error(f"Project {self.project} is not valid: {e}")
            return False
        return True

    @property
    def facilities(self) -> List[dict]:
        """Return all facilities from project card as list of dicts."""
        if any(["transit" in t for t in self.change_types]):
            CardLogger.warning("Transit project doesn't have services.")
            return []
        f = list(_findkeys(self.__dict__, "facility"))
        if not f:
            raise ProjectCardValidationError("Couldn't find facility in project card")
        return f

    @property
    def facility(self) -> Union[str, dict]:
        """Return facility part of project card or "multiple" if more than one."""
        f = self.facilities
        if len(f) > 1:
            return "multiple"
        return f[0]

    @property
    def services(self) -> List[dict]:
        """Return all services from project card as list of dicts."""
        if any(["roadway" in t for t in self.change_types]):
            CardLogger.warning("Roadway project doesn't have services.")
            return []
        s = list(_findkeys(self.__dict__, "service"))
        if not s:
            raise ProjectCardValidationError("Couldn't find service in project card")
        return s

    @property
    def service(self) -> Union[str, dict]:
        """Return service part of from project card or "multiple" if more than one."""
        s = self.services
        if len(s) > 1:
            return "multiple"
        return s[0]

    @property
    def all_transit_property_changes(self) -> List[dict]:
        """Return all transit property changes from project card."""
        if not any(["transit_property_change" in t for t in self.change_types]):
            CardLogger.warning(f"Project {self.project} doesn't have transit property changes.")
            return []
        tp = list(_findkeys(self.__dict__, "transit_property_change"))
        p = [i["property_changes"] for i in tp]
        return p

    @property
    def transit_property_change(self) -> Union[str, dict]:
        """Return transit property change from project card or "multiple if more than one."""
        p = self.all_transit_property_changes
        if len(p) > 1:
            return "multiple"
        return p[0]

    @property
    def all_transit_routing_changes(self) -> List[dict]:
        """Return all transit routing changes from project card."""
        if not any(["transit_routing_change" in t for t in self.change_types]):
            CardLogger.warning(f"Project {self.project} doesn't have routing changes.")
            return []
        r = list(_findkeys(self.__dict__, "routing"))
        CardLogger.debug(f"transit routing change: {r}")
        return r

    @property
    def transit_routing_change(self) -> Union[str, dict]:
        """Return transit routing change from project card."""
        p = self.all_transit_routing_changes
        if len(p) > 1:
            return "multiple"
        return p[0]

    @property
    def change_types(self) -> List[str]:
        """Returns list of all change types from project/subproject."""
        if self.sub_projects:
            return [sp.change_type for sp in self.sub_projects]
        _ignore = [
            "project",
            "tags",
            "notes",
            "dependencies",
            "self_obj_type",
            "sub_projects",
            "file",
        ]
        type_keys = [k for k in self.__dict__.keys() if k not in _ignore]
        if not type_keys:
            raise ProjectCardValidationError(f"Couldn't find type of project card {self.project}")
        return type_keys

    @property
    def change_type(self) -> str:
        """Return single change type if single project or "multiple" if >1 subproject."""
        t = self.change_types
        if len(t) > 1:
            return "multiple"
        return t[0]


class SubProject(ProjectCard):
    """Representation of a SubProject within a ProjectCard.

    Attributes:
        parent_project: reference to parent ProjectCard object
        type:  project type
        tags: reference to parent project card tags
        dependencies: reference to parent project card's dependencies
        project: reference to the name of the parent project card's name
        facility: facility selection dictionary
        property_changes:property_changes dictionary
    """

    def __init__(self, sp_dictionary: dict, parent_project: ProjectCard):
        """Constructor for SubProject object.

        Args:
            sp_dictionary (dict): dictionary of sub-project attributes contained within "changes"
                list of parent projet card
            parent_project (ProjectCard): ProjectCard object for parent project card
        """
        self.parent_project = parent_project

        if not len(sp_dictionary) == 1:
            CardLogger.debug(f"Invalid sp_dictionary with !=1 keys: {sp_dictionary.keys()}")
            raise SubprojectValidationError(
                f"Subproject of {parent_project.project} \
                 should only have one change. Did you forget to indent the rest of this change?."
            )
        self._change_type = list(sp_dictionary.keys())[0]
        self.__dict__.update(sp_dictionary)
        self.sub_projects = []

    @property
    def change_type(self) -> str:
        """Return change type from subproject."""
        return self._change_type

    @property
    def project(self) -> str:
        """Return project name from parent project card."""
        return self.parent_project.project

    @property
    def dependencies(self) -> str:
        """Return dependencies from parent project card."""
        return self.parent_project.dependencies

    @property
    def tags(self) -> str:
        """Return tags from parent project card."""
        return self.parent_project.tags

    @property
    def facility(self) -> dict:
        """Return facility dictionary from subproject."""
        if "facility" not in self.__dict__:
            raise SubprojectValidationError(
                f"Couldn't find facility in subproject in project card\
                                            {self.parent_project.project}"
            )
        return self.__dict__["facility"]

    @property
    def valid(self) -> bool:
        """Check if subproject is valid."""
        return self.parent_project.valid
