from os import environ
import re

import attr
import click
import git
import gitdb.exc

from .exc import UpgradeFailed


@attr.s(slots=True)
class Deployment:

    stack = attr.ib(default=None)
    services = attr.ib(default=None)
    repo = attr.ib(default=None)
    old_version = attr.ib(default=None)
    new_version = attr.ib(default=None)
    is_limited = attr.ib(default=False)

    def load_from_settings(self, settings):
        from . import rancher  # here to prevent circular importing

        self.stack = rancher.Stack.from_name(settings["stack"])
        self.services = [
            self.stack.service_from_name(service) for service in settings["service"]
        ]

        old_image = self.services[0].json()["launchConfig"]["imageUuid"]

        if settings["new_image"]:
            self.old_version = old_image.split(":")[-1]
            self.new_version = settings["new_image"].split(":")[-1]
        else:
            version_matches = re.findall(r"\b[0-9a-f]{40}\b", old_image)

            if not version_matches:
                click.secho(
                    "Your existing image seems to have no commit hash in its tag "
                    + "for me to be able to upgrade to the new commit, "
                    + f"but it's currently tagged as just :{old_image.split(':')[-1]} "
                    + click.style("(๑′°︿°๑)", bold=True),
                    err=True,
                    fg="red",
                )
                raise UpgradeFailed()
            elif len(version_matches) > 1:
                click.secho(
                    "Your existing image seems to have multiple commit hashes in its tag, "
                    + f"I don't know which one to replace, {', or'.join(version_matches)}! "
                    + click.style("(。•́︿•̀。)", bold=True),
                    err=True,
                    fg="red",
                )
                raise UpgradeFailed()

            self.old_version = version_matches[0]
            self.new_version = settings["new_commit"]

        self.check_preconditions()

    @property
    def id(self):
        return self.old_version + self.new_version

    @property
    def commits(self):
        if self.is_disconnected:
            return [self.new_commit]
        elif self.is_redeploy:
            return []
        elif self.is_rollback:
            return list(
                self.repo.iter_commits(self.old_version + "..." + self.new_version)
            )
        return reversed(
            list(self.repo.iter_commits(self.old_version + "..." + self.new_version))
        )

    @property
    def old_commit(self):
        return self.repo.commit(self.old_version)

    @property
    def new_commit(self):
        return self.repo.commit(self.new_version)

    @property
    def is_rollback(self):
        return self.new_commit.committed_date < self.old_commit.committed_date

    @property
    def is_redeploy(self):
        return self.old_version == self.new_version

    @property
    def is_disconnected(self):
        """True if no path can be found from old commit to new commit."""
        try:
            return not (
                self.repo.is_ancestor(self.old_version, self.new_version)
                or self.repo.is_ancestor(self.new_version, self.old_version)
            )
        except git.GitCommandError:  # old commit was probably removed by force push or other black magic
            return True

    def check_preconditions(self):
        from . import settings  # avoiding circular imports

        try:
            self.repo = git.Repo(environ["CI_PROJECT_DIR"])
        except git.NoSuchPathError:
            click.secho(
                f"You are not running crane in a Git repository. "
                "crane is running in limited mode, all hooks have been disabled. "
                "It is highly recommended you use Git references for your deployments.",
                err=True,
                fg="red",
            )
            self.is_limited = True
            return
        try:
            self.new_commit
        except (gitdb.exc.BadName, ValueError):
            click.secho(
                f"The new version you specified, {self.new_version}, is not a valid git reference! "
                "crane is running in limited mode, all hooks have been disabled. "
                "It is highly recommended you use Git references for your deployments.",
                err=True,
                fg="red",
            )
            self.is_limited = True
            return

        for service in self.services:
            if (
                self.old_version not in service.json()["launchConfig"]["imageUuid"]
                and not settings["new_image"]
            ):
                click.secho(
                    "All selected services must have the same commit SHA. "
                    "Please manually change their versions so they are all the same, and then retry the upgrade.",
                    err=True,
                    fg="red",
                )
                raise UpgradeFailed()