# (c) cavaliba.com - tests / views / ipam_subnet IP-list CSV download

import os
import shutil
import tempfile

from django.test import TestCase, override_settings
from django.urls import reverse

import app_home.cache as cache
from app_data.data import Instance
from app_data.loader import load_broker
from app_data.models import DataFile
from app_user.models import SirenePermission
from tests import helper

# the builtin ipam_subnet schema sets p_read: p_ipam_read (220_ipam.yml), which
# overrides the generic p_data_read - see Schema.has_read_permission()
BASE_PERMS = ["p_data_access", "p_ipam_read"]
SUBNET = "198.51.100.0/24"


@override_settings(CAVALIBA_AUTH_MODE="unittest")
class ViewsIpamIpListDownloadTest(TestCase):
    def _isolate_filestore(self):
        """Point the filestore at a throwaway directory for this test.

        The view writes a real blob, and the test DB is dropped at the end of the
        run while the blob is not - so without this every run would leave orphans
        behind in the shared CAVALIBA_FILESTORE.
        """

        rootdir = tempfile.mkdtemp(prefix="cavaliba_test_filestore_")
        override = override_settings(CAVALIBA_FILESTORE=rootdir)
        override.enable()
        self.addCleanup(override.disable)
        self.addCleanup(shutil.rmtree, rootdir, ignore_errors=True)
        return rootdir

    def setUp(self):
        self.rootdir = self._isolate_filestore()
        cache.clear()

        helper.add_user_with_perm(login="unittest", perms=BASE_PERMS)
        helper.add_schema(
            classname="ipdltest",
            field_definition={"ip_address": {"dataformat": "ipv4", "displayname": "IP"}},
        )
        helper.add_instance(classname="ipam_subnet", keyname=SUBNET)
        for last in (10, 20, 30):
            helper.add_instance(
                classname="ipam_ip",
                fields={"displayname": f"198.51.100.{last}", "description": f"desc{last}"},
            )
        helper.add_instance(
            classname="ipdltest", keyname="host01", fields={"ip_address": "198.51.100.20"}
        )

        self.subnet = Instance.from_keyname(classname="ipam_subnet", keyname=SUBNET)

    def _set_perms(self, perms):
        for perm_name in perms:
            SirenePermission.objects.get_or_create(keyname=perm_name)
        load_broker(
            datalist=[
                {
                    "classname": "role",
                    "_action": "update",
                    "keyname": "role_unittest",
                    "permissions": perms,
                }
            ],
            aaa={"perms": ["p_role_update"]},
        )
        cache.clear()
        self.client.session.flush()

    def _url(self, iid=None):
        return reverse(
            "app_data:ipam_subnet_ip_list_download", kwargs={"id": iid or self.subnet.id}
        )

    def _body(self, response):
        return b"".join(response.streaming_content).decode()

    def test_full_list_downloaded(self):
        response = self.client.get(self._url())

        self.assertEqual(response.status_code, 200)
        body = self._body(response)
        self.assertEqual(
            body.splitlines(),
            [
                "ip,collected,description,referrers",
                "198.51.100.10,no,desc10,",
                "198.51.100.20,no,desc20,ipdltest:host01",
                "198.51.100.30,no,desc30,",
            ],
        )

    @override_settings(CAVALIBA_IPAM_IP_LIST_MAX=1)
    def test_download_holds_every_ip_the_page_truncated(self):
        # the point of the feature: the inline table stops at CAVALIBA_IPAM_IP_LIST_MAX,
        # the file does not
        response = self.client.get(self._url())

        body = self._body(response)
        self.assertIn("198.51.100.10", body)
        self.assertIn("198.51.100.30", body)

    def test_filename_offered_for_download(self):
        response = self.client.get(self._url())

        # as_attachment, not inline: a browser must save the file, not open it
        self.assertIn("attachment;", response["Content-Disposition"])
        self.assertIn("ipam_ip_list_198-51-100-0_24.csv", response["Content-Disposition"])

    def test_datafile_recorded_with_an_expiry(self):
        response = self.client.get(self._url())
        body = self._body(response)

        datafile = DataFile.objects.get(filename="ipam_ip_list_198-51-100-0_24.csv")
        self.assertIsNotNone(datafile.not_after)
        self.assertEqual(datafile.size, len(body))
        self.assertTrue(datafile.hash)
        self.assertTrue(os.path.isfile(datafile.filepath))

    def test_each_request_builds_its_own_datafile(self):
        self.client.get(self._url())
        self.client.get(self._url())

        self.assertEqual(
            DataFile.objects.filter(filename="ipam_ip_list_198-51-100-0_24.csv").count(), 2
        )

    def test_empty_subnet_downloads_a_header_only_file(self):
        helper.add_instance(classname="ipam_subnet", keyname="192.0.2.0/24")
        other = Instance.from_keyname(classname="ipam_subnet", keyname="192.0.2.0/24")

        response = self.client.get(self._url(iid=other.id))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(self._body(response).splitlines(), ["ip,collected,description,referrers"])

    def test_unknown_id_is_404(self):
        response = self.client.get(self._url(iid=999999))

        self.assertEqual(response.status_code, 404)

    def test_other_schema_is_404(self):
        other = Instance.from_keyname(classname="ipdltest", keyname="host01")

        response = self.client.get(self._url(iid=other.id))

        self.assertEqual(response.status_code, 404)

    def test_without_read_permission_redirected(self):
        self._set_perms(["p_data_access"])

        response = self.client.get(self._url())

        self.assertEqual(response.status_code, 302)
        self.assertFalse(DataFile.objects.exists())

    def test_without_p_data_access_redirected(self):
        self._set_perms([])

        response = self.client.get(self._url())

        self.assertEqual(response.status_code, 302)
        self.assertFalse(DataFile.objects.exists())
