"""Tests for general API tests for the plugin app."""

from django.urls import reverse

from rest_framework.exceptions import NotFound

from InvenTree.unit_test import InvenTreeAPITestCase, PluginMixin
from plugin.api import check_plugin
from plugin.models import PluginConfig


class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
    """Tests the plugin API endpoints"""

    roles = [
        'admin.add',
        'admin.view',
        'admin.change',
        'admin.delete',
    ]

    def setUp(self):
        """Setup for all tests."""
        self.MSG_NO_PKG = 'Either packagename of URL must be provided'

        self.PKG_NAME = 'minimal'
        self.PKG_URL = 'git+https://github.com/geoffrey-a-reed/minimal'
        super().setUp()

    def test_plugin_install(self):
        """Test the plugin install command."""
        url = reverse('api-plugin-install')

        # valid - Pypi
        data = self.post(
            url,
            {
                'confirm': True,
                'packagename': self.PKG_NAME
            },
            expected_code=201,
        ).data

        self.assertEqual(data['success'], True)

        # valid - github url
        data = self.post(
            url,
            {
                'confirm': True,
                'url': self.PKG_URL
            },
            expected_code=201,
        ).data

        self.assertEqual(data['success'], True)

        # valid - github url and package name
        data = self.post(
            url,
            {
                'confirm': True,
                'url': self.PKG_URL,
                'packagename': 'minimal',
            },
            expected_code=201,
        ).data
        self.assertEqual(data['success'], True)

        # invalid tries
        # no input
        self.post(url, {}, expected_code=400)

        # no package info
        data = self.post(url, {
            'confirm': True,
        }, expected_code=400).data

        self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper())
        self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper())

        # not confirmed
        self.post(url, {
            'packagename': self.PKG_NAME
        }, expected_code=400)

        data = self.post(url, {
            'packagename': self.PKG_NAME,
            'confirm': False,
        }, expected_code=400).data

        self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())

    def test_plugin_activate(self):
        """Test the plugin activate."""

        test_plg = self.plugin_confs.first()

        def assert_plugin_active(self, active):
            self.assertEqual(PluginConfig.objects.all().first().active, active)

        # Should not work - not a superuser
        response = self.client.post(reverse('api-plugin-activate'), {}, follow=True)
        self.assertEqual(response.status_code, 403)

        # Make user superuser
        self.user.is_superuser = True
        self.user.save()

        # Deactivate plugin
        test_plg.active = False
        test_plg.save()

        # Activate plugin with detail url
        assert_plugin_active(self, False)
        response = self.client.patch(reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.id}), {}, follow=True)
        self.assertEqual(response.status_code, 200)
        assert_plugin_active(self, True)

        # Deactivate plugin
        test_plg.active = False
        test_plg.save()

        # Activate plugin
        assert_plugin_active(self, False)
        response = self.client.patch(reverse('api-plugin-activate'), {'pk': test_plg.pk}, follow=True)
        self.assertEqual(response.status_code, 200)
        assert_plugin_active(self, True)

    def test_admin_action(self):
        """Test the PluginConfig action commands."""
        url = reverse('admin:plugin_pluginconfig_changelist')

        test_plg = self.plugin_confs.first()
        # deactivate plugin
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [test_plg.pk],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # deactivate plugin - deactivate again -> nothing will happen but the nothing 'changed' function is triggered
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [test_plg.pk],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # activate plugin
        response = self.client.post(url, {
            'action': 'plugin_activate',
            'index': 0,
            '_selected_action': [test_plg.pk],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # save to deactivate a plugin
        response = self.client.post(reverse('admin:plugin_pluginconfig_change', args=(test_plg.pk, )), {
            '_save': 'Save',
        }, follow=True)
        self.assertEqual(response.status_code, 200)

    def test_model(self):
        """Test the PluginConfig model."""
        # check mixin registry
        plg = self.plugin_confs.first()
        mixin_dict = plg.mixins()
        self.assertIn('base', mixin_dict)
        self.assertDictContainsSubset({'base': {'key': 'base', 'human_name': 'base'}}, mixin_dict)

        # check reload on save
        with self.assertWarns(Warning) as cm:
            plg_inactive = self.plugin_confs.filter(active=False).first()
            plg_inactive.active = True
            plg_inactive.save()
        self.assertEqual(cm.warning.args[0], 'A reload was triggered')

    def test_check_plugin(self):
        """Test check_plugin function."""

        # No argument
        with self.assertRaises(NotFound) as exc:
            check_plugin(plugin_slug=None, plugin_pk=None)
        self.assertEqual(str(exc.exception.detail), 'Plugin not specified')

        # Wrong with slug
        with self.assertRaises(NotFound) as exc:
            check_plugin(plugin_slug='123abc', plugin_pk=None)
        self.assertEqual(str(exc.exception.detail), "Plugin '123abc' not installed")

        # Wrong with pk
        with self.assertRaises(NotFound) as exc:
            check_plugin(plugin_slug=None, plugin_pk='123')
        self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed")

    def test_plugin_settings(self):
        """Test plugin settings access via the API"""

        # Ensure we have superuser permissions
        self.user.is_superuser = True
        self.user.save()

        # Activate the 'sample' plugin via the API
        cfg = PluginConfig.objects.filter(key='sample').first()
        url = reverse('api-plugin-detail-activate', kwargs={'pk': cfg.pk})
        self.client.patch(url, {}, expected_code=200)

        # Valid plugin settings endpoints
        valid_settings = [
            'SELECT_PART',
            'API_KEY',
            'NUMERICAL_SETTING',
        ]

        for key in valid_settings:
            response = self.get(
                reverse('api-plugin-setting-detail', kwargs={
                    'plugin': 'sample',
                    'key': key
                }))

            self.assertEqual(response.data['key'], key)

        # Test that an invalid setting key raises a 404 error
        response = self.get(
            reverse('api-plugin-setting-detail', kwargs={
                'plugin': 'sample',
                'key': 'INVALID_SETTING'
            }),
            expected_code=404
        )

        # Test that a protected setting returns hidden value
        response = self.get(
            reverse('api-plugin-setting-detail', kwargs={
                'plugin': 'sample',
                'key': 'PROTECTED_SETTING'
            }),
            expected_code=200
        )

        self.assertEqual(response.data['value'], '***')

        # Test that we can update a setting value
        response = self.patch(
            reverse('api-plugin-setting-detail', kwargs={
                'plugin': 'sample',
                'key': 'NUMERICAL_SETTING'
            }),
            {
                'value': 456
            },
            expected_code=200
        )

        self.assertEqual(response.data['value'], '456')

        # Retrieve the value again
        response = self.get(
            reverse('api-plugin-setting-detail', kwargs={
                'plugin': 'sample',
                'key': 'NUMERICAL_SETTING'
            }),
            expected_code=200
        )

        self.assertEqual(response.data['value'], '456')