from unittest.mock import ANY, patch

import sh
from ploigos_step_runner import StepResult
from ploigos_step_runner.step_implementers.create_container_image import \
    SourceToImage
from tests.helpers.base_step_implementer_test_case import \
    BaseStepImplementerTestCase


class TestStepImplementerSourceToImage_step_implementer_config_defaults(
    BaseStepImplementerTestCase
):
    def test_result(self):
        self.assertEqual(
            SourceToImage.step_implementer_config_defaults(),
            {
                'context': '.',
                'tls-verify': True,
                's2i-additional-arguments': [],
                's2i-loglevel': 1
            }
        )

class TestStepImplementerSourceToImage__required_config_or_result_keys(
    BaseStepImplementerTestCase
):
    def test_result(self):
        self.assertEqual(
            SourceToImage._required_config_or_result_keys(),
            [
                'context',
                'tls-verify',
                's2i-builder-image',
                'organization',
                'service-name',
                'application-name'
            ]
        )

@patch('sh.s2i', create=True)
@patch('ploigos_step_runner.step_implementers.create_container_image.source_to_image.inspect_container_image')
@patch('ploigos_step_runner.step_implementers.create_container_image.source_to_image.container_registries_login')
@patch.object(
    SourceToImage,
    'write_working_file',
    return_value='/mock/working/s2i-context/Containerfile.s2i-gen'
)
@patch.object(
    SourceToImage,
    'create_working_dir_sub_dir',
    return_value='/mock/working/s2i-context'
)
class TestStepImplementerSourceToImage__run_step(
    BaseStepImplementerTestCase
):
    def create_step_implementer(
            self,
            step_config={},
            workflow_result=None,
            parent_work_dir_path=''
    ):
        return self.create_given_step_implementer(
            step_implementer=SourceToImage,
            step_config=step_config,
            step_name='create-container-image',
            implementer='SourceToImage',
            workflow_result=workflow_result,
            parent_work_dir_path=parent_work_dir_path
        )

    def test_success_builder_image_with_scripts_url_label(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_container_registries_login.assert_called_once_with(
            registries=None,
            containers_config_auth_file='create-container-image/container-auth.json',
            containers_config_tls_verify=True
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_given_containers_config_auth_file(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42',
            'containers-config-auth-file': '/mock/mock-regs-auths.json'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='/mock/mock-regs-auths.json'
        )
        mock_container_registries_login.assert_called_once_with(
            registries=None,
            containers_config_auth_file='/mock/mock-regs-auths.json',
            containers_config_tls_verify=True
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_error_inspecting_builder_image_to_get_scripts_url(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.side_effect = RuntimeError('mock buildah inspect error')

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.message = "WARNING: failed to inspect s2i builder image" \
            " (mock.io/awesome-image:v42) to dynamically determine image scripts url." \
            " S2I default will be used: mock buildah inspect error\n"
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_error_finding_label_on_build_image_inspection_details(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {}

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.message = "WARNING: failed to find s2i scripts url label" \
            " (io.openshift.s2i.scripts-url) on s2i builder image" \
            " (mock.io/awesome-image:v42) to dynamically determine image scripts url." \
            " S2I default will be used: Could not find key ('OCIv1').\n"
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_no_tls_verify_bool(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42',
            'tls-verify': False
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_no_tls_verify_str(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42',
            'tls-verify': 'false'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_given_image_scripts_url(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42',
            's2i-image-scripts-url': 'image:///mock-user-given/s2i'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_not_called()
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///mock-user-given/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_fail_builder_image_with_scripts_url_label(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_s2i.build.side_effect = sh.ErrorReturnCode('s2i', b'mock out', b'mock err')
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.success = False
        expected_step_result.message = "Issue invoking s2i build:" \
            " \n\n  RAN: s2i\n\n  STDOUT:\nmock out\n\n  STDERR:\nmock err"
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )

    def test_success_error_container_registries_login(
        self,
        mock_create_working_dir_sub_dir,
        mock_write_working_file,
        mock_container_registries_login,
        mock_inspect_container_image,
        mock_s2i
    ):
        # setup
        step_config = {
            's2i-builder-image': 'mock.io/awesome-image:v42'
        }
        step_implementer = self.create_step_implementer(
            step_config=step_config
        )

        # setup mocks
        mock_inspect_container_image.return_value = {
            'OCIv1': {
                'config': {
                    'Labels': {
                        'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
                    }
                }
            }
        }
        mock_container_registries_login.side_effect = RuntimeError('mock error loging in')

        # run test
        actual_step_result= step_implementer._run_step()

        # validate
        expected_step_result = StepResult(
            step_name='create-container-image',
            sub_step_name='SourceToImage',
            sub_step_implementer_name='SourceToImage'
        )
        expected_step_result.message = "WARNING: error authenticating with" \
            " container image registries to be able to pull s2i builder image" \
            " to inspect for image scripts url: mock error loging in\n"
        expected_step_result.add_artifact(
            name='imagespecfile',
            value='/mock/working/s2i-context/Containerfile.s2i-gen',
            description='File defining the container image to build generated by s2i'
        )
        expected_step_result.add_artifact(
            name='context',
            value='/mock/working/s2i-context',
            description='Context to use when building the imagespecfile generated by S2I.'
        )
        self.assertEqual(actual_step_result, expected_step_result)

        mock_inspect_container_image.assert_called_once_with(
            container_image_uri='mock.io/awesome-image:v42',
            containers_config_auth_file='create-container-image/container-auth.json'
        )
        mock_container_registries_login.assert_called_once_with(
            registries=None,
            containers_config_auth_file='create-container-image/container-auth.json',
            containers_config_tls_verify=True
        )
        mock_s2i.build.assert_called_once_with(
            '.',
            'mock.io/awesome-image:v42',
            '--loglevel', 1,
            '--tlsverify',
            '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
            '--image-scripts-url', 'image:///moock-image-label/s2i',
            _out=ANY,
            _err=ANY,
            _tee='err'
        )