from typing import Optional, Union

from django.core.exceptions import ObjectDoesNotExist

from db.models.repos import CodeReference
from libs.repos import git


def _get_repo_code_reference(repo: 'Repo', commit: str = None) -> Optional['CodeReference']:
    if commit:
        try:
            return CodeReference.objects.get(repo=repo,
                                             commit=commit)
        except ObjectDoesNotExist:
            return None

    # If no commit is provided we get the last commit, and save new ref if not found
    try:
        last_commit = repo.last_commit
    except ValueError:
        return None

    code_reference, _ = CodeReference.objects.get_or_create(repo=repo,
                                                            commit=last_commit[0])
    return code_reference


def _get_external_repo_code_reference(repo: 'ExternalRepo',
                                      commit: str = None) -> Optional['CodeReference']:
    # Fetch latest
    git.fetch(git_url=repo.git_clone_url, repo_path=repo.path)
    if commit:
        try:
            return CodeReference.objects.get(external_repo=repo,
                                             git_url=repo.git_url,
                                             commit=commit)
        except ObjectDoesNotExist:
            return None

    # If no commit is provided we get the last commit, and save new ref if not found
    try:
        last_commit = repo.last_commit
    except ValueError:
        return None

    code_reference, _ = CodeReference.objects.get_or_create(external_repo=repo,
                                                            git_url=repo.git_url,
                                                            commit=last_commit[0])
    return code_reference


def get_code_reference(instance, commit: str = None) -> Optional['CodeReference']:
    project = instance.project

    if not project.has_code:
        return None

    if project.has_external_repo:  # pylint:disable=no-else-return
        repo = project.external_repo
        return _get_external_repo_code_reference(repo=repo, commit=commit)
    else:
        repo = project.repo
        return _get_repo_code_reference(repo=repo, commit=commit)


RefModel = Union['Experiment',
                 'ExperimentGroup',
                 'Job',
                 'BuildJob',
                 'TensorboardJob',
                 'NotebookJob']


def assign_code_reference(instance: RefModel, commit: str = None) -> RefModel:
    if instance.code_reference is not None or instance.specification is None:
        return instance
    build = instance.specification.build if instance.specification else None
    if not commit and build:
        commit = build.ref
    code_reference = get_code_reference(instance=instance, commit=commit)
    if code_reference:
        instance.code_reference = code_reference

    return instance

@@ -963,122 +963,335 @@
 '%5D:%0A
+%0A
     
-# Fetch latest%0A    git.fetch(git_url=repo.git_clone_url, repo_path=repo.path)%0A    if commit:%0A        try:%0A    
+def get_or_create(ref):%0A        code_references = CodeReference.objects.filter(external_repo=repo,%0A                                                       git_url=repo.git_url,%0A                                                       commit=ref)%0A        if code_references.exists():%0A            return code_references.last()%0A
     
@@ -1323,19 +1323,22 @@
 objects.
-get
+create
 (externa
@@ -1342,33 +1342,32 @@
 rnal_repo=repo,%0A
- 
                 
@@ -1464,82 +1464,158 @@
     
- 
 commit=
-commit)%0A        except ObjectDoesNotExist:%0A            return None
+ref)%0A%0A    # Fetch latest%0A    git.fetch(git_url=repo.git_clone_url, repo_path=repo.path)%0A    if commit:%0A        return get_or_create(ref=commit)
 %0A%0A  
@@ -1797,274 +1797,48 @@
     
-code_reference, _ = CodeReference.objects.get_or_create(external_repo=repo,%0A                                                            git_url=repo.git_url,%0A                                                            commit=last_commit%5B0%5D)%0A    return code_reference
+return get_or_create(ref=last_commit%5B0%5D)
 %0A%0A%0Ad

Update external repo code ref setting