import unittest
import json as py_json

from flask import url_for
from endpoints.api import api
from app import app, storage
from initdb import setup_database_for_testing, finished_database_for_testing
from data import model

NO_ACCESS_USER = 'freshuser'
READ_ACCESS_USER = 'reader'
ADMIN_ACCESS_USER = 'devtable'
PUBLIC_USER = 'public'
RANDOM_USER = 'randomuser'
OUTSIDE_ORG_USER = 'outsideorg'

ADMIN_ROBOT_USER = 'devtable+dtrobot'

ORGANIZATION = 'buynlarge'

REPO = 'devtable/simple'
PUBLIC_REPO = 'public/publicrepo'
RANDOM_REPO = 'randomuser/randomrepo'

OUTSIDE_ORG_REPO = 'outsideorg/coolrepo'

ORG_REPO = 'buynlarge/orgrepo'
ANOTHER_ORG_REPO = 'buynlarge/anotherorgrepo'

# Note: The shared repo has devtable as admin, public as a writer and reader as a reader.
SHARED_REPO = 'devtable/shared'

class TestImageSharing(unittest.TestCase):
  def setUp(self):
    setup_database_for_testing(self)
    self.app = app.test_client()
    self.ctx = app.test_request_context()
    self.ctx.__enter__()

  def tearDown(self):
    finished_database_for_testing(self)
    self.ctx.__exit__(True, None, None)

  def createStorage(self, docker_image_id, repository=REPO, username=ADMIN_ACCESS_USER):
    repository_obj = model.repository.get_repository(repository.split('/')[0],
                                                     repository.split('/')[1])
    preferred = storage.preferred_locations[0]
    image = model.image.find_create_or_link_image(docker_image_id, repository_obj, username, {},
                                                  preferred)
    image.storage.uploading = False
    image.storage.save()
    return image.storage

  def assertSameStorage(self, docker_image_id, existing_storage, repository=REPO,
                        username=ADMIN_ACCESS_USER):
    new_storage = self.createStorage(docker_image_id, repository, username)
    self.assertEquals(existing_storage.id, new_storage.id)

  def assertDifferentStorage(self, docker_image_id, existing_storage, repository=REPO,
                             username=ADMIN_ACCESS_USER):
    new_storage = self.createStorage(docker_image_id, repository, username)
    self.assertNotEquals(existing_storage.id, new_storage.id)


  def test_same_user(self):
    """ The same user creates two images, each which should be shared in the same repo. This is a
      sanity check. """

    # Create a reference to a new docker ID => new image.
    first_storage = self.createStorage('first-image')

    # Create a reference to the same docker ID => same image.
    self.assertSameStorage('first-image', first_storage)

    # Create a reference to another new docker ID => new image.
    second_storage_id = self.createStorage('second-image')

    # Create a reference to that same docker ID => same image.
    self.assertSameStorage('second-image', second_storage_id)

    # Make sure the images are different.
    self.assertNotEquals(first_storage, second_storage_id)


  def test_no_user_private_repo(self):
    """ If no user is specified (token case usually), then no sharing can occur on a private repo. """
    # Create a reference to a new docker ID => new image.
    first_storage = self.createStorage('the-image', username=None, repository=SHARED_REPO)

    # Create a areference to the same docker ID, but since no username => new image.
    self.assertDifferentStorage('the-image', first_storage, username=None, repository=RANDOM_REPO)


  def test_no_user_public_repo(self):
    """ If no user is specified (token case usually), then no sharing can occur on a private repo except when the image is first public. """
    # Create a reference to a new docker ID => new image.
    first_storage = self.createStorage('the-image', username=None, repository=PUBLIC_REPO)

    # Create a areference to the same docker ID. Since no username, we'd expect different but the first image is public so => shaed image.
    self.assertSameStorage('the-image', first_storage, username=None, repository=RANDOM_REPO)


  def test_different_user_same_repo(self):
    """ Two different users create the same image in the same repo. """

    # Create a reference to a new docker ID under the first user => new image.
    first_storage = self.createStorage('the-image', username=PUBLIC_USER, repository=SHARED_REPO)

    # Create a reference to the *same* docker ID under the second user => same image.
    self.assertSameStorage('the-image', first_storage, username=ADMIN_ACCESS_USER, repository=SHARED_REPO)


  def test_different_repo_no_shared_access(self):
    """ Neither user has access to the other user's repository. """

    # Create a reference to a new docker ID under the first user => new image.
    first_storage = self.createStorage('the-image', username=RANDOM_USER, repository=RANDOM_REPO)

    # Create a reference to the *same* docker ID under the second user => new image.
    second_storage_id = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=REPO)

    # Verify that the users do not share storage.
    self.assertNotEquals(first_storage, second_storage_id)


  def test_public_than_private(self):
    """ An image is created publicly then used privately, so it should be shared. """

    # Create a reference to a new docker ID under the first user => new image.
    first_storage = self.createStorage('the-image', username=PUBLIC_USER, repository=PUBLIC_REPO)

    # Create a reference to the *same* docker ID under the second user => same image, since the first was public.
    self.assertSameStorage('the-image', first_storage, username=ADMIN_ACCESS_USER, repository=REPO)


  def test_private_than_public(self):
    """ An image is created privately then used publicly, so it should *not* be shared. """

    # Create a reference to a new docker ID under the first user => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=REPO)

    # Create a reference to the *same* docker ID under the second user => new image, since the first was private.
    self.assertDifferentStorage('the-image', first_storage, username=PUBLIC_USER, repository=PUBLIC_REPO)


  def test_different_repo_with_access(self):
    """ An image is created in one repo (SHARED_REPO) which the user (PUBLIC_USER) has access to. Later, the
        image is created in another repo (PUBLIC_REPO) that the user also has access to. The image should
        be shared since the user has access.
    """
    # Create the image in the shared repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=SHARED_REPO)

    # Create the image in the other user's repo, but since the user (PUBLIC) still has access to the shared
    # repository, they should reuse the storage.
    self.assertSameStorage('the-image', first_storage, username=PUBLIC_USER, repository=PUBLIC_REPO)


  def test_org_access(self):
    """ An image is accessible by being a member of the organization. """

    # Create the new image under the org's repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=ORG_REPO)

    # Create an image under the user's repo, but since the user has access to the organization => shared image.
    self.assertSameStorage('the-image', first_storage, username=ADMIN_ACCESS_USER, repository=REPO)

    # Ensure that the user's robot does not have access, since it is not on the permissions list for the repo.
    self.assertDifferentStorage('the-image', first_storage, username=ADMIN_ROBOT_USER, repository=SHARED_REPO)


  def test_org_access_different_user(self):
    """ An image is accessible by being a member of the organization. """

    # Create the new image under the org's repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=ORG_REPO)

    # Create an image under a user's repo, but since the user has access to the organization => shared image.
    self.assertSameStorage('the-image', first_storage, username=PUBLIC_USER, repository=PUBLIC_REPO)

    # Also verify for reader.
    self.assertSameStorage('the-image', first_storage, username=READ_ACCESS_USER, repository=PUBLIC_REPO)


  def test_org_no_access(self):
    """ An image is not accessible if not a member of the organization. """

    # Create the new image under the org's repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=ORG_REPO)

    # Create an image under a user's repo. Since the user is not a member of the organization => new image.
    self.assertDifferentStorage('the-image', first_storage, username=RANDOM_USER, repository=RANDOM_REPO)


  def test_org_not_team_member_with_access(self):
    """ An image is accessible to a user specifically listed as having permission on the org repo. """

    # Create the new image under the org's repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=ORG_REPO)

    # Create an image under a user's repo. Since the user has read access on that repo, they can see the image => shared image.
    self.assertSameStorage('the-image', first_storage, username=OUTSIDE_ORG_USER, repository=OUTSIDE_ORG_REPO)


  def test_org_not_team_member_with_no_access(self):
    """ A user that has access to one org repo but not another and is not a team member. """

    # Create the new image under the org's repo => new image.
    first_storage = self.createStorage('the-image', username=ADMIN_ACCESS_USER, repository=ANOTHER_ORG_REPO)

    # Create an image under a user's repo. The user doesn't have access to the repo (ANOTHER_ORG_REPO) so => new image.
    self.assertDifferentStorage('the-image', first_storage, username=OUTSIDE_ORG_USER, repository=OUTSIDE_ORG_REPO)

  def test_no_link_to_uploading(self):
    still_uploading = self.createStorage('an-image', repository=PUBLIC_REPO)
    still_uploading.uploading = True
    still_uploading.save()

    self.assertDifferentStorage('an-image', still_uploading)


if __name__ == '__main__':
  unittest.main()