import shutil

from dbt.clients import system
from dbt.deps.base import PinnedPackage, UnpinnedPackage
from dbt.contracts.project import (
    ProjectPackageMetadata,
    LocalPackage,
)
from dbt.events.functions import fire_event
from dbt.events.types import DepsCreatingLocalSymlink, DepsSymlinkNotAvailable
from dbt.config.project import PartialProject, Project
from dbt.config.renderer import PackageRenderer


class LocalPackageMixin:
    def __init__(self, local: str) -> None:
        super().__init__()
        self.local = local

    @property
    def name(self):
        return self.local

    def source_type(self):
        return "local"


class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
    def __init__(self, local: str) -> None:
        super().__init__(local)

    def get_version(self):
        return None

    def nice_version_name(self):
        return "<local @ {}>".format(self.local)

    def resolve_path(self, project):
        return system.resolve_path_from_base(
            self.local,
            project.project_root,
        )

    def _fetch_metadata(
        self, project: Project, renderer: PackageRenderer
    ) -> ProjectPackageMetadata:
        partial = PartialProject.from_project_root(self.resolve_path(project))
        return partial.render_package_metadata(renderer)

    def install(self, project, renderer):
        src_path = self.resolve_path(project)
        dest_path = self.get_installation_path(project, renderer)

        can_create_symlink = system.supports_symlinks()

        if system.path_exists(dest_path):
            if not system.path_is_symlink(dest_path):
                system.rmdir(dest_path)
            else:
                system.remove_file(dest_path)

        if can_create_symlink:
            fire_event(DepsCreatingLocalSymlink())
            system.make_symlink(src_path, dest_path)

        else:
            fire_event(DepsSymlinkNotAvailable())
            shutil.copytree(src_path, dest_path)


class LocalUnpinnedPackage(LocalPackageMixin, UnpinnedPackage[LocalPinnedPackage]):
    @classmethod
    def from_contract(cls, contract: LocalPackage) -> "LocalUnpinnedPackage":
        return cls(local=contract.local)

    def incorporate(self, other: "LocalUnpinnedPackage") -> "LocalUnpinnedPackage":
        return LocalUnpinnedPackage(local=self.local)

    def resolved(self) -> LocalPinnedPackage:
        return LocalPinnedPackage(local=self.local)
