# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Test the App components provides by backups app.
"""

import json
from unittest.mock import call, patch

import pytest

from plinth import kvstore

from .. import components
from ..components import BackupRestore

# pylint: disable=protected-access


@pytest.fixture(name='backup_restore')
def fixture_backup_restore():
    """Fixture to create a domain type after clearing all existing ones."""
    value = {'files': ['a', 'b'], 'directories': ['a', 'b']}
    services = ['service-1', {'type': 'system', 'name': 'service-2'}]
    settings = ['setting-1', 'setting-2']
    return BackupRestore('test-backup-restore', config=value, data=value,
                         secrets=value, services=services, settings=settings)


@pytest.mark.parametrize('section', [
    None,
    {
        'directories': ['a', 'b']
    },
    {
        'files': ['a', 'b']
    },
    {
        'directories': ['a'],
        'files': ['a']
    },
    {
        'extra': 'value'
    },
])
def test_valid_directories_and_files(section):
    """Test that valid values of files and directories."""
    components._validate_directories_and_files(section)


@pytest.mark.parametrize('section', [
    'invalid',
    10,
    ['invalid'],
    {
        'files': None
    },
    {
        'files': 10
    },
    {
        'files': {}
    },
    {
        'files': [10],
    },
    {
        'files': [None],
    },
    {
        'files': [[]],
    },
    {
        'directories': None
    },
    {
        'directories': [10],
    },
])
def test_invalid_directories_and_files(section):
    """Test that invalid values of files and directories."""
    with pytest.raises(AssertionError):
        components._validate_directories_and_files(section)


@pytest.mark.parametrize('services', [
    None,
    [],
    ['service'],
    [{
        'type': 'uwsgi',
        'name': 'service'
    }],
    [{
        'type': 'system',
        'name': 'service'
    }],
    [{
        'type': 'apache',
        'name': 'service',
        'kind': 'config'
    }],
    [{
        'type': 'apache',
        'name': 'service',
        'kind': 'site'
    }],
    [{
        'type': 'apache',
        'name': 'service',
        'kind': 'module'
    }],
])
def test_valid_services(services):
    """Test that valid values of services."""
    components._validate_services(services)


@pytest.mark.parametrize('services', [
    10,
    'invalid',
    [10],
    [[]],
    [{}],
    [{
        'type': 'invalid',
        'name': 'service'
    }],
    [{
        'type': 10,
        'name': 'service'
    }],
    [{
        'type': 'system',
        'name': 10
    }],
    [{
        'type': 'system',
        'name': None
    }],
    [{
        'type': 'system',
        'name': []
    }],
    [{
        'type': 'apache',
        'name': 'service'
    }],
    [{
        'type': 'apache',
        'name': 'service',
        'kind': 'invalid-kind'
    }],
])
def test_invalid_services(services):
    """Test that invalid values of services."""
    with pytest.raises((AssertionError, KeyError)):
        components._validate_services(services)


def test_backup_restore_init_default_arguments():
    """Test initialization of the backup restore object."""
    component = BackupRestore('test-backup-restore')
    assert component.component_id == 'test-backup-restore'
    assert component.config == {}
    assert component.data == {}
    assert component.secrets == {}
    assert component.services == []
    assert component.settings == []
    assert not component.has_data


@pytest.mark.parametrize('key', ['config', 'data', 'secrets'])
def test_backup_restore_init(key):
    """Test initialization of the backup restore object."""
    with pytest.raises(AssertionError):
        BackupRestore('test-backup-restore', **{key: 'invalid-value'})

    value = {'files': ['a', 'b'], 'directories': ['a', 'b']}
    component = BackupRestore('test-backup-restore', **{key: value})
    assert getattr(component, key) == value
    assert component.has_data


def test_backup_restore_init_services():
    """Test initialization of the backup restore object."""
    with pytest.raises(AssertionError):
        BackupRestore('test-backup-restore', services='invalid-value')

    services = ['service-1', {'type': 'system', 'name': 'service-2'}]
    component = BackupRestore('test-backup-restore', services=services)
    assert component.services == services
    assert not component.has_data


def test_backup_restore_init_settings():
    """Test initialization of the backup restore object."""
    with pytest.raises(AssertionError):
        BackupRestore('test-backup-restore', settings='invalid-value')

    settings = ['setting1', 'setting2']
    component = BackupRestore('test-backup-restore', settings=settings)
    assert component.settings == settings
    assert component.has_data
    assert component.data == {}

    component.app_id = 'testapp'
    settings_file = '/var/lib/plinth/backups-data/testapp-settings.json'
    assert component.data == {'files': [settings_file]}


def test_backup_restore_equal(backup_restore):
    """Test equality operator on the backup restore object."""
    assert backup_restore == BackupRestore('test-backup-restore')
    assert backup_restore != BackupRestore('test-different')


def test_backup_restore_manifest(backup_restore):
    """Test manifest retrieval from backup restore object."""
    manifest = backup_restore.manifest
    assert isinstance(manifest, dict)
    assert manifest['config'] == backup_restore.config
    assert manifest['data'] == backup_restore.data
    assert manifest['secrets'] == backup_restore.secrets
    assert manifest['services'] == backup_restore.services
    assert manifest['settings'] == backup_restore.settings

    assert BackupRestore('test-backup-restore').manifest == {}


def test_backup_restore_hooks(backup_restore):
    """Test running hooks on backup restore object."""
    packet = None
    backup_restore.backup_post(packet)
    backup_restore.restore_pre(packet)


@pytest.mark.django_db
@patch('plinth.actions.superuser_run')
def test_backup_restore_backup_pre(run, backup_restore):
    """Test running backup-pre hook."""
    packet = None
    kvstore.set('setting-1', 'value-1')
    backup_restore.app_id = 'testapp'

    component = BackupRestore('test-backup-restore')
    component.backup_pre(packet)
    run.assert_has_calls([])

    backup_restore.backup_pre(packet)
    input_ = {'setting-1': 'value-1'}
    run.assert_has_calls([
        call('backups', ['dump-settings', '--app-id', 'testapp'],
             input=json.dumps(input_).encode())
    ])


@pytest.mark.django_db
@patch('plinth.actions.superuser_run')
def test_backup_restore_restore_post(run, backup_restore):
    """Test running restore-post hook."""
    packet = None
    backup_restore.app_id = 'testapp'

    component = BackupRestore('test-backup-restore')
    component.restore_post(packet)
    run.assert_has_calls([])

    output = {'setting-1': 'value-1'}
    run.return_value = json.dumps(output)
    backup_restore.restore_post(packet)
    run.assert_has_calls(
        [call('backups', ['load-settings', '--app-id', 'testapp'])])

    assert kvstore.get('setting-1') == 'value-1'
    with pytest.raises(Exception):
        kvstore.get('setting-2')
