import argparse
import copy
import unittest
from unittest import mock

from schema import SchemaError

import skelebot as sb

class TestArtifactory(unittest.TestCase):
    artifcatory = None

    artifactoryDict = {
        "url": "test",
        "repo": "test",
        "path": "path",
        "artifacts": [1, 2]
    }

    artifactDict = {
        "name": "test",
        "file": "test"
    }

    def setUp(self):
        artifact = sb.components.artifactory.Artifact("test", "test.pkl")
        self.artifactory = sb.components.artifactory.Artifactory([artifact], "artifactory.test.com", "ml", "test")

    def test_addParsers(self):
        parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
        subparsers = parser.add_subparsers(dest="job")
        subparsers = self.artifactory.addParsers(subparsers)

        self.assertNotEqual(subparsers.choices["push"], None)
        self.assertNotEqual(subparsers.choices["pull"], None)

    @mock.patch('os.rename')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_push_conflict(self, mock_artifactory, mock_rename):
        config = sb.objects.config.Config(version="1.0.0")
        args = argparse.Namespace(job="push", force=False, artifact='test', user='sean', token='abc123')

        expectedException = "This artifact version already exists. Please bump the version or use the force parameter (-f) to overwrite the artifact."

        try:
            self.artifactory.execute(config, args)
            self.fail("Exception Not Thrown")
        except Exception as exc:
            self.assertEqual(str(exc), expectedException)
            mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v1.0.0.pkl", auth=('sean', 'abc123'))

    @mock.patch('shutil.copyfile')
    @mock.patch('os.remove')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_push_error(self, mock_artifactory, mock_remove, mock_copy):
        mock_path = mock.MagicMock()
        mock_path.deploy_file = mock.MagicMock(side_effect=KeyError('foo'))
        mock_artifactory.return_value = mock_path

        config = sb.objects.config.Config(version="1.0.0")
        args = argparse.Namespace(job="push", force=True, artifact='test', user='sean', token='abc123')

        with self.assertRaises(KeyError):
            self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v1.0.0.pkl", auth=('sean', 'abc123'))
        mock_copy.assert_called_with("test.pkl", "test_v1.0.0.pkl")
        mock_remove.assert_called_with("test_v1.0.0.pkl")

    @mock.patch('shutil.copyfile')
    @mock.patch('os.remove')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_push(self, mock_artifactory, mock_remove, mock_copy):
        config = sb.objects.config.Config(version="1.0.0")
        args = argparse.Namespace(job="push", force=True, artifact='test', user='sean', token='abc123')

        self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v1.0.0.pkl", auth=('sean', 'abc123'))
        mock_copy.assert_called_with("test.pkl", "test_v1.0.0.pkl")
        mock_remove.assert_called_with("test_v1.0.0.pkl")

    @mock.patch('skelebot.components.artifactory.input')
    @mock.patch('builtins.open')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_pull(self, mock_artifactory, mock_open, mock_input):
        mock_input.return_value = "abc"

        config = sb.objects.config.Config(version="1.0.0")
        args = argparse.Namespace(job="pull", version='0.1.0', artifact='test', user=None, token=None, override=False)

        self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v0.1.0.pkl", auth=("abc", "abc"))
        mock_open.assert_called_with("test_v0.1.0.pkl", "wb")

    @mock.patch('skelebot.components.artifactory.input')
    @mock.patch('builtins.open')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_pull_lcv(self, mock_artifactory, mock_open, mock_input):
        mock_apath = mock_artifactory.return_value
        mock_input.return_value = "abc"
        mock_apath.__iter__.return_value = ["test_v1.1.0", "test_v0.2.4", "test_v1.0.0", "test_v2.0.1"]

        config = sb.objects.config.Config(version="1.0.9")
        args = argparse.Namespace(job="pull", version='LATEST', artifact='test', user=None, token=None, override=False)

        self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v1.0.0.pkl", auth=("abc", "abc"))
        mock_open.assert_called_with("test_v1.0.0.pkl", "wb")

    @mock.patch('skelebot.components.artifactory.input')
    @mock.patch('builtins.open')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_pull_lcv_not_found(self, mock_artifactory, mock_open, mock_input):
        mock_apath = mock_artifactory.return_value
        mock_input.return_value = "abc"
        mock_apath.__iter__.return_value = ["test_v1.1.0", "test_v0.2.4", "test_v1.0.0", "test_v2.0.1"]

        config = sb.objects.config.Config(version="3.0.9")
        args = argparse.Namespace(job="pull", version='LATEST', artifact='test', user=None, token=None, override=False)

        try:
            self.artifactory.execute(config, args)
            self.fail("Exception Not Thrown")
        except RuntimeError as err:
            self.assertEqual(str(err), "No Compatible Version Found")

    @mock.patch('skelebot.components.artifactory.input')
    @mock.patch('builtins.open')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_pull_override_and_lcv(self, mock_artifactory, mock_open, mock_input):
        mock_apath = mock_artifactory.return_value
        mock_input.return_value = "abc"
        mock_apath.__iter__.return_value = ["test_v1.1.0", "test_v0.2.4", "test_v1.0.0", "test_v2.0.1"]

        config = sb.objects.config.Config(version="0.6.9")
        args = argparse.Namespace(job="pull", version='LATEST', artifact='test', user=None, token=None, override=True)

        self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v0.2.4.pkl", auth=("abc", "abc"))
        mock_open.assert_called_with("test.pkl", "wb")

    @mock.patch('skelebot.components.artifactory.input')
    @mock.patch('artifactory.ArtifactoryPath')
    def test_execute_pull_not_found(self, mock_artifactory, mock_input):
        mock_input.return_value = "abc"
        path = mock_artifactory.return_value
        path.exists.return_value = False

        config = sb.objects.config.Config(version="1.0.0")
        args = argparse.Namespace(job="pull", version='0.1.0', artifact='test', user=None, token=None, override=False)

        self.artifactory.execute(config, args)

        mock_artifactory.assert_called_with("artifactory.test.com/ml/test/test_v0.1.0.pkl", auth=("abc", "abc"))

    def test_validate_valid(self):
        try:
            sb.components.artifactory.Artifactory.validate(self.artifactoryDict)
        except:
            self.fail("Validation Raised Exception Unexpectedly")

        try:
            sb.components.artifactory.Artifact.validate(self.artifactDict)
        except:
            self.fail("Validation Raised Exception Unexpectedly")

    def test_validate_mising(self):
        artifactoryDict = copy.deepcopy(self.artifactoryDict)
        del artifactoryDict['url']
        del artifactoryDict['repo']
        del artifactoryDict['path']

        try:
            sb.components.artifactory.Artifactory.validate(artifactoryDict)
        except SchemaError as error:
            self.assertEqual(error.code, "Missing keys: 'path', 'repo', 'url'")

        artifactDict = copy.deepcopy(self.artifactDict)
        del artifactDict['name']
        del artifactDict['file']

        try:
            sb.components.artifactory.Artifact.validate(artifactDict)
        except SchemaError as error:
            self.assertEqual(error.code, "Missing keys: 'file', 'name'")

    def validate_error_artifactory(self, attr, reset, expected):
        artifactoryDict = copy.deepcopy(self.artifactoryDict)
        artifactoryDict[attr] = reset

        try:
            sb.components.artifactory.Artifactory.validate(artifactoryDict)
        except SchemaError as error:
            self.assertEqual(error.code, "Artifactory '{attr}' must be a {expected}".format(attr=attr, expected=expected))

    def validate_error_artifact(self, attr, reset, expected):
        artifactDict = copy.deepcopy(self.artifactDict)
        artifactDict[attr] = reset

        try:
            sb.components.artifactory.Artifact.validate(artifactDict)
        except SchemaError as error:
            self.assertEqual(error.code, "Artifact '{attr}' must be a {expected}".format(attr=attr, expected=expected))

    def test_invalid(self):
        self.validate_error_artifactory('url', 123, 'String')
        self.validate_error_artifactory('repo', 123, 'String')
        self.validate_error_artifactory('path', 123, 'String')
        self.validate_error_artifactory('artifacts', 123, 'List')
        self.validate_error_artifact('name', 123, 'String')
        self.validate_error_artifact('file', 123, 'String')

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