# BFH Science Self-Service Portal
# Copyright (C) 2025-2026 IKUS Software
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

from unittest import mock

import responses
from parameterized import parameterized
from responses import matchers

from . import WebCase


def _mock_subprocess(output=""):
    mock_process = mock.MagicMock()
    mock_process.returncode = 0
    mock_process.stdout = output
    mock_process.stderr = ""
    return mock_process


TICKET_CREATE_URL = 'https://znuny.example.com/otrs/nph-genericinterface.pl/Webservice/rest-api/TicketCreate'


class StorageHandlerTest(WebCase):

    login = True

    default_config = {
        'new-storage-cmd': '/bin/true',
        'znuny-ticket-create-url': TICKET_CREATE_URL,
        'znuny-userlogin': 'myuser',
        'znuny-password': 'test123',
    }

    @parameterized.expand(
        [
            (
                'valid_user',
                {'storage_usage': 'user', 'storage_user_name': 'not-used', 'storage_size': '<1TB'},
                {
                    'SSP_USER_USERNAME': 'test-user',
                    'SSP_USER_DN': 'cn=test-user,ou=Staff,ou=Accounts,dc=bfh',
                    'SSP_USER_EMAIL': 'test@example.com',
                    'SSP_STORAGE_USAGE': 'user',
                    'SSP_STORAGE_NAME': 'Users/test-user',
                    'SSP_STORAGE_USER_NAME': 'test-user',
                    'SSP_STORAGE_SIZE': '<1TB',
                    'SSP_STORAGE_TYPE': 'smb',
                },
            ),
            (
                'valid_group',
                {'storage_usage': 'group', 'storage_group_name': 'my-group-name', 'storage_size': '<10TB'},
                {
                    'SSP_USER_USERNAME': 'test-user',
                    'SSP_USER_DN': 'cn=test-user,ou=Staff,ou=Accounts,dc=bfh',
                    'SSP_USER_EMAIL': 'test@example.com',
                    'SSP_STORAGE_USAGE': 'group',
                    'SSP_STORAGE_NAME': 'MyDepartement/my-group-name',
                    'SSP_STORAGE_GROUP_NAME': 'my-group-name',
                    'SSP_STORAGE_SIZE': '<10TB',
                    'SSP_STORAGE_TYPE': 'smb',
                },
            ),
        ]
    )
    @mock.patch('subprocess.run', return_value=_mock_subprocess())
    def test_create_storage(self, name, data, expected_env, mock_exec_requests):
        # Given a user
        # When user request storage
        self.getPage("/dashboard/create-storage", body=data, method='POST')
        # Then user redirect to dashboard
        self.assertStatus(303)
        self.assertHeaderItemValue('Location', f'{self.baseurl}/dashboard')
        # Then new page show success
        self.getPage("/dashboard/")
        self.assertStatus(200)
        self.assertInBody("Request executed successfully.")
        # Then storage command get executed.
        mock_exec_requests.assert_called_once_with(
            '/bin/true',
            env=mock.ANY,
            shell=True,
            check=True,
            stdin=-3,
            stdout=-1,
            stderr=-2,
            text=True,
        )
        self.assertEqual(mock_exec_requests.call_args.kwargs['env'], expected_env)

    @parameterized.expand(
        [
            (
                'valid_storage_name_read',
                {'storage_name': 'LFE/Services/group1', 'permission': 'read', 'reason': 'N/A'},
                'mail',
                'SSP: LNI Storage - Request read access to storage: LFE/Services/group1',
            ),
            (
                'valid_storage_name_write',
                {'storage_name': 'LFE/Services/group1', 'permission': 'write', 'reason': 'N/A'},
                'mail',
                'SSP: LNI Storage - Request write access to storage: LFE/Services/group1',
            ),
            (
                'valid_without_manager',
                {'storage_name': 'LFE/Services/group-without-manager', 'permission': 'read', 'reason': 'N/A'},
                'its',
                'SSP: LNI Storage - Request read access to storage: LFE/Services/group-without-manager',
            ),
            (
                'invalid_storage_name',
                {'storage_name': 'LFE/Services/invalid', 'permission': 'read', 'reason': 'N/A'},
                False,
            ),
            (
                'invalid_permission',
                {'storage_name': 'LFE/Services/group1', 'permission': 'invalid', 'reason': 'N/A'},
                False,
            ),
            (
                'invalid_reason',
                {'storage_name': 'LFE/Services/group1', 'permission': 'invalid', 'reason': ''},
                False,
            ),
        ]
    )
    def test_request_storage_access(self, name, data, expect_success, expected_subject=None):
        self.listener.send_mail.reset_mock()
        # Mock ticket creation if required.
        self.responses.assert_all_requests_are_fired = False
        self.responses.add(
            responses.POST,
            TICKET_CREATE_URL,
            json={'TicketID': '1234', 'TicketNumber': '0987654321'},
            status=200,
            match=[
                matchers.json_params_matcher(
                    {"Ticket": {"Title": expected_subject, "Service": "LNI Storage"}}, strict_match=False
                )
            ],
        )
        # Given a storage.
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.services-group1.read,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': ['Services/group1'],
                'bfhGroupManager': ['cn=olivia,ou=Staff,ou=Accounts,dc=bfh'],
            },
        )
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.services-group-without-manager.read,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': ['Services/group-without-manager'],
                'bfhGroupManager': [],
            },
        )
        self.conn.strategy.add_entry(
            'cn=olivia,ou=Staff,ou=Accounts,dc=bfh',
            {
                'uid': ['olivia'],
                'givenName': ['Olivia'],
                'sn': ['Bennett'],
                'mail': ['olivia@example.com'],
                'objectClass': ['person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount'],
            },
        )
        # When user request access to that storage
        self.getPage(
            "/dashboard/request-storage-access",
            body=data,
            method='POST',
        )
        # Then user is redirected to dashboard page.
        self.assertStatus(303)
        self.assertHeaderItemValue('Location', f'{self.baseurl}/dashboard')
        if expect_success:
            # Then dashboard page show success
            self.getPage("/dashboard/")
            self.assertStatus(200)
            if expect_success == 'mail':
                self.assertInBody("Success! Your request has been sent. A copy has been emailed to you.")
                # Email send to the storage managers - cc requester
                self.listener.send_mail.assert_any_call(
                    to='Olivia Bennett <olivia@example.com>' if expect_success == 'mail' else 'test@example.com',
                    cc='test@example.com' if expect_success == 'mail' else None,
                    reply_to='test@example.com',
                    subject=expected_subject,
                    message=mock.ANY,
                )
            else:
                self.assertInBody(
                    'Success! Your request has been logged as ticket <a href="https://znuny.example.com/otrs/index.pl?Action=AgentTicketZoom;TicketID=1234" target="_blank">#0987654321</a>. A copy has been emailed to you.'
                )
                # Email send to requester
                self.listener.send_mail.assert_any_call(
                    to='test@example.com',
                    cc=None,
                    reply_to=None,
                    subject=f"[Ticket#0987654321] {expected_subject}",
                    message=mock.ANY,
                )
        else:
            self.responses.assert_all_requests_are_fired = False
            # Then email are not sent.
            self.assertEqual(self.listener.send_mail.call_count, 0)
            # Then dashboard page show failure
            self.getPage("/dashboard/")
            self.assertStatus(200)
            self.assertNotInBody("Success! Your request has been sent. A copy has been emailed to you.")
            self.assertInBody("alert alert-danger")

    def test_storage_json_empty(self):
        # Given a user without data
        # When querying storage list
        self.getJson("/dashboard/storage.json")
        # Then the list is empty.
        self.assertBody('{"data": []}')

    def test_storage_json_with_data(self):
        # Given a user with data
        self.conn.strategy.add_entry(
            'cn=myuser,ou=Staff,ou=Accounts,dc=bfh',
            {
                'userPassword': self.password,
                'uid': ['myuser'],
                'objectClass': ['person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount'],
                'bfhCephDirectory': 'Users/myuser',
                'givenName': ['My'],
                'sn': ['User'],
                'mail': ['myuser@example.com'],
                'memberOf': [
                    'cn=IDM.perm.storage.services-my-share-name.read,ou=Security,ou=Groups,dc=bfh',
                    'cn=IDM.perm.storage.second-share-name.read,ou=Security,ou=Groups,dc=bfh',
                    'cn=IDM.perm.storage.second-share-name.write,ou=Security,ou=Groups,dc=bfh',
                ],
            },
        )
        self.conn.strategy.add_entry(
            'cn=amanager,ou=Staff,ou=Accounts,dc=bfh',
            {
                'uid': ['amanager'],
                'objectClass': ['person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount'],
                'bfhCephDirectory': 'Users/myuser',
                'givenName': ['A'],
                'sn': ['Manager'],
                'memberOf': [
                    'cn=IDM.perm.storage.second-share-name.write,ou=Security,ou=Groups,dc=bfh',
                ],
            },
        )
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.services-my-share-name.read,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': 'Services/My-Share-Name',
            },
        )
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.second-share-name.write,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': 'Second/Share-Name',
                'bfhGroupManager': [
                    'cn=myuser,ou=Staff,ou=Accounts,dc=bfh, cn=amanager,ou=Staff,ou=Accounts,dc=bfh, cn=invalid,ou=Staff,ou=Accounts,dc=bfh'
                ],
            },
        )

        # When querying storage list
        self._login('myuser', self.password)
        data = self.getJson("/dashboard/storage.json")

        # Then list of storage is returned.
        self.assertEqual(
            data,
            {
                'data': [
                    {
                        'name': 'LFE/Users/myuser',
                        'protocol': 'SMB',
                        'permission': 'write',
                        'usage': 'user',
                        'owners': [{'name': 'My User', 'username': 'myuser', 'email': 'myuser@example.com'}],
                        'mountpath_linux': 'smb://bfh.ch/data/LFE/Users/myuser',
                        'mountpath_linux_managed': '/home/myuser or /mnt/data/user/myuser',
                        'mountpath_win': '\\\\bfh.ch\\data\\LFE\\Users\\myuser',
                        'mountpath_win_managed': 'P:\\LFE\\Users\\myuser',
                        'group': None,
                        'manage_url': None,
                        'service': 'LNI Storage',
                    },
                    {
                        'name': 'LFE/Second/Share-Name',
                        'protocol': 'SMB',
                        'permission': 'write',
                        'usage': 'group',
                        'owners': [
                            {'name': 'A Manager', 'username': 'amanager', 'email': None},
                            {'name': 'My User', 'username': 'myuser', 'email': 'myuser@example.com'},
                        ],
                        'mountpath_linux': 'smb://bfh.ch/data/LFE/Second/Share-Name',
                        'mountpath_linux_managed': '/mnt/data/group/Second/Share-Name',
                        'mountpath_win': '\\\\bfh.ch\\data\\LFE\\Second\\Share-Name',
                        'mountpath_win_managed': 'P:\\LFE\\Second\\Share-Name',
                        'group': 'IDM.perm.storage.second-share-name.write',
                        'manage_url': 'https://idm.bfh.ch/group/IDM.perm.storage.second-share-name.write',
                        'service': 'LNI Storage',
                    },
                    {
                        'name': 'LFE/Services/My-Share-Name',
                        'protocol': 'SMB',
                        'permission': 'read',
                        'usage': 'group',
                        'owners': [],
                        'mountpath_linux': 'smb://bfh.ch/data/LFE/Services/My-Share-Name',
                        'mountpath_linux_managed': '/mnt/data/group/Services/My-Share-Name',
                        'mountpath_win': '\\\\bfh.ch\\data\\LFE\\Services\\My-Share-Name',
                        'mountpath_win_managed': 'P:\\LFE\\Services\\My-Share-Name',
                        'group': None,
                        'manage_url': None,
                        'service': 'LNI Storage',
                    },
                ]
            },
        )

    def test_all_storage_json__with_data(self):
        # Given 2 existing storage
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.services-group1.read,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': 'Services/group1',
            },
        )
        self.conn.strategy.add_entry(
            'cn=IDM.perm.storage.second-group2.write,ou=Security,ou=Groups,dc=bfh',
            {
                'objectClass': ['group'],
                'bfhCephDirectory': 'Second/group2',
            },
        )
        # When querying the list of all storage.
        data = self.getJson("/dashboard/all_storage.json")

        # Then the storage are listed (not sorted)
        self.assertEqual(data, {'data': mock.ANY})
        self.assertEqual(sorted(data['data']), ['LFE/Second/group2', 'LFE/Services/group1'])
