from pathlib import Path
from typing import Dict
from typing import Optional
from typing import TYPE_CHECKING

from pkm_buildsys.utils import toml

from pkm_main.utils.helpers import canonicalize_name
from pkm_main.utils.helpers import module_name

if TYPE_CHECKING:
    from pkm_buildsys.pyproject.project import Project

POETRY_DEFAULT = """\
[tool.poetry]
name = ""
version = ""
description = ""
authors = []
license = ""
readme = ""
packages = []

[tool.poetry.dependencies]

[tool.poetry.group.dev.dependencies]
"""

BUILD_SYSTEM_MIN_VERSION: Optional[str] = "0.1.2"
BUILD_SYSTEM_MAX_VERSION: Optional[str] = None


class Layout:
    ACCEPTED_README_FORMATS = {"md", "rst"}

    def __init__(
            self,
            project: str,
            version: str = "0.1.0",
            description: str = "",
            readme_format: str = "md",
            author: Optional[str] = None,
            license: Optional[str] = None,  # noqa
            python: str = "*",
            dependencies: Optional[Dict[str, str]] = None,
            dev_dependencies: Optional[Dict[str, str]] = None,
    ):
        self._project = canonicalize_name(project).replace(".", "-")
        self._package_path_relative = Path(
            *(module_name(part) for part in canonicalize_name(project).split("."))
        )
        self._package_name = ".".join(self._package_path_relative.parts)
        self._version = version
        self._description = description

        self._readme_format = readme_format.lower()
        if self._readme_format not in self.ACCEPTED_README_FORMATS:
            raise ValueError(
                "Invalid readme format '{}', use one of {}.".format(
                    readme_format, ", ".join(self.ACCEPTED_README_FORMATS)
                )
            )

        self._license = license
        self._python = python
        self._dependencies = dependencies or {}
        self._dev_dependencies = dev_dependencies or {}

        if not author:
            author = "Your Name <you@example.com>"

        self._author = author

    @property
    def basedir(self) -> Path:
        return Path()

    @property
    def package_path(self) -> Path:
        return self.basedir / self._package_path_relative

    def get_package_include(self) -> Optional["Dict"]:
        package = {}

        include = self._package_path_relative.parts[0]
        package["include"]= include

        if self.basedir != Path():
            package["from"] = self.basedir.as_posix()
        else:
            if include == self._project:
                # package include and package name are the same,
                # packages table is redundant here.
                return None

        return package

    def create(self, path: "Path", with_tests: bool = True) -> None:
        path.mkdir(parents=True, exist_ok=True)

        self._create_default(path)
        self._create_readme(path)

        if with_tests:
            self._create_tests(path)

        self._write_poetry(path)

    def generate_poetry_content(
            self, original: Optional["Project"] = None
    ) -> str:
        template = POETRY_DEFAULT

        content,dumps = toml.loads(template)

        poetry_content = content["tool"]["poetry"]
        poetry_content["name"] = self._project
        poetry_content["version"] = self._version
        poetry_content["description"] = self._description
        poetry_content["authors"].append(self._author)

        if self._license:
            poetry_content["license"] = self._license
        else:
            del poetry_content["license"]

        poetry_content["readme"] = "README.{}".format(self._readme_format)
        packages = self.get_package_include()
        if packages:
            poetry_content["packages"].append(packages)
        else:
            del poetry_content["packages"]

        poetry_content["dependencies"]["python"] = self._python

        for dep_name, dep_constraint in self._dependencies.items():
            poetry_content["dependencies"][dep_name] = dep_constraint

        if self._dev_dependencies:
            for dep_name, dep_constraint in self._dev_dependencies.items():
                poetry_content["group"]["dev"]["dependencies"][
                    dep_name
                ] = dep_constraint
        else:
            del poetry_content["group"]

        # Add build system
        build_system = {}
        build_system_version = ""

        if BUILD_SYSTEM_MIN_VERSION is not None:
            build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION
        if BUILD_SYSTEM_MAX_VERSION is not None:
            if build_system_version:
                build_system_version += ","
            build_system_version += "<" + BUILD_SYSTEM_MAX_VERSION

        build_system["requires"] = ["relaxed-poetry-core" + build_system_version]
        build_system["build-backend"] = "poetry.core.masonry.api"

        content["build-system"] = build_system

        if original and original.path.exists():
            odata, dumps = toml.load(original.path)
            odata.update(content)
            content = odata

        content = dumps(content)
        return content

    def _create_default(self, path: "Path", src: bool = True) -> None:
        package_path = path / self.package_path
        package_path.mkdir(parents=True)

        package_init = package_path / "__init__.py"
        package_init.touch()

    def _create_readme(self, path: "Path") -> "Path":
        readme_file = path.joinpath("README.{}".format(self._readme_format))
        readme_file.touch()
        return readme_file

    @staticmethod
    def _create_tests(path: "Path") -> None:
        tests = path / "tests"
        tests.mkdir()

        tests_init = tests / "__init__.py"
        tests_init.touch(exist_ok=False)

    def _write_poetry(self, path: "Path") -> None:
        content = self.generate_poetry_content()

        poetry = path / "pyproject.toml"

        with poetry.open("w", encoding="utf-8") as f:
            f.write(content)
