# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Tests related to workers views."""
from django.test import RequestFactory
from django.urls import reverse
from rest_framework import status

from debusine.db.context import context
from debusine.db.playground import scenarios
from debusine.tasks.models import WorkerType
from debusine.test.django import TestCase
from debusine.web.views.tests.utils import ViewTestMixin
from debusine.web.views.workers import WorkersListView


class WorkersListViewTests(ViewTestMixin, TestCase):
    """Tests for WorkersListView."""

    scenario = scenarios.DefaultContext(set_current=True)

    def setUp(self) -> None:
        """Set up objects."""
        super().setUp()
        self.factory = RequestFactory()

    def test_template_with_workers(self) -> None:
        """Test template contains expected information."""
        with context.disable_permission_checks():
            workspace = self.playground.create_workspace(
                name="Public", public=True
            )

        worker_external = self.playground.create_worker()
        worker_external.name = "a-first-one"
        worker_external.concurrency = 3
        worker_external.save()

        worker_external.mark_connected()

        worker_celery = self.playground.create_worker(
            worker_type=WorkerType.CELERY
        )
        worker_celery.name = "b-second-one"
        worker_celery.save()

        worker_signing = self.playground.create_worker(
            worker_type=WorkerType.SIGNING
        )
        worker_signing.name = "c-third-one"
        worker_signing.save()

        work_request_1 = self.playground.create_work_request(
            task_name="noop",
            worker=worker_external,
            mark_running=True,
            workspace=workspace,
        )
        work_request_2 = self.playground.create_work_request(
            task_name="noop",
            worker=worker_external,
            mark_running=True,
            workspace=workspace,
        )

        response = self.client.get(reverse("workers:list"))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        tree = self.assertHTMLValid(response)

        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        # Assert names
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[0],
            f'<td>{worker_external.name} '
            f'(<abbr title="External">E</abbr>)</td>',
        )
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[1].td[0],
            f'<td>{worker_celery.name} '
            f'(<abbr title="Celery">C</abbr>)</td>',
        )
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[2].td[0],
            f'<td>{worker_signing.name} '
            f'(<abbr title="Signing">S</abbr>)</td>',
        )

        # Assert status for external worker: "Running ..."
        path_1 = work_request_1.get_absolute_url()
        path_2 = work_request_2.get_absolute_url()
        external_running = (
            f'<td> '
            f'<a href="{path_1}">noop</a><br/>'
            f'<a href="{path_2}">noop</a><br/>'
            f'</td>'
        )
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2], external_running
        )

    def test_status_disabled(self) -> None:
        """View shows "Disabled" (worker's token is disabled)."""
        worker = self.playground.create_worker()
        assert worker.token is not None
        worker.token.enabled = False
        worker.token.save()

        response = self.client.get(reverse("workers:list"))

        tree = self.assertHTMLValid(response)
        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2],
            '<td><span class="badge text-bg-danger">Disabled</span></td>',
        )

    def test_status_idle(self) -> None:
        """View shows "Idle" (enabled worker, not running any work request)."""
        worker = self.playground.create_worker()
        worker.mark_connected()

        response = self.client.get(reverse("workers:list"))

        tree = self.assertHTMLValid(response)
        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2],
            '<td> <span class="badge text-bg-success">Idle</span> </td>',
        )

    def test_status_disconnected(self) -> None:
        """View shows "Disconnected" (enabled worker, not connected)."""
        self.playground.create_worker()

        response = self.client.get(reverse("workers:list"))

        tree = self.assertHTMLValid(response)
        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2],
            '<td> <span class="badge text-bg-warning">'
            'Disconnected</span> </td>',
        )

    def test_template_no_workers(self) -> None:
        """Test template renders "No workers registered in this..."."""
        response = self.client.get(reverse("workers:list"))

        self.assertContains(
            response,
            "<p>No workers registered in this debusine instance.</p>",
            html=True,
        )

    def test_template_with_private_work_request_anonymous(self) -> None:
        """Test template hides private work requests from anonymous viewers."""
        with context.disable_permission_checks():
            workspace = self.playground.create_workspace(name="Private")

        worker_external = self.playground.create_worker()
        worker_external.name = "a-first-one"
        worker_external.concurrency = 3
        worker_external.save()

        worker_external.mark_connected()

        self.playground.create_work_request(
            task_name="noop",
            worker=worker_external,
            mark_running=True,
            workspace=workspace,
        )

        response = self.client.get(reverse("workers:list"))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        tree = self.assertHTMLValid(response)
        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        # Assert status for external worker: "Running ..."
        external_running = '<td>Private Task<br/></td>'
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2], external_running
        )

    def test_template_with_private_work_request_authenticated(self) -> None:
        """Test template shows private work requests to the authenticated."""
        with context.disable_permission_checks():
            workspace = self.playground.create_workspace(name="Private")

        worker_external = self.playground.create_worker()
        worker_external.name = "a-first-one"
        worker_external.concurrency = 3
        worker_external.save()

        worker_external.mark_connected()

        work_request = self.playground.create_work_request(
            task_name="noop",
            worker=worker_external,
            mark_running=True,
            workspace=workspace,
        )

        self.client.force_login(self.playground.get_default_user())
        response = self.client.get(reverse("workers:list"))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        tree = self.assertHTMLValid(response)

        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        # Assert status for external worker: "Running ..."
        path = work_request.get_absolute_url()
        external_running = f'<td><a href="{path}">noop</a><br/></td>'
        self.assertHTMLContentsEquivalent(
            table.tbody.tr[0].td[2], external_running
        )

    def test_celery_worker(self) -> None:
        """Template shows "-" in Last Seen and Status for Celery workers."""
        self.playground.create_worker(WorkerType.CELERY)

        response = self.client.get(reverse("workers:list"))

        tree = self.assertHTMLValid(response)

        table = self.assertHasElement(tree, "//table[@id='worker-list']")

        # Last Seen At is "-"
        self.assertHTMLContentsEquivalent(table.tbody.tr[0].td[1], "<td>-</td>")

        # Status is "-"
        self.assertHTMLContentsEquivalent(table.tbody.tr[0].td[2], "<td>-</td>")

    def test_get_ordering(self) -> None:
        """The workers list can be ordered."""
        for field in (
            "name",
            "worker_type",
            "registered_at",
            "last_seen_at",
            "enabled",
        ):
            for asc in ["0", "1"]:
                response = self.client.get(
                    f"/-/status/workers/?order={field}&asc={asc}"
                )

            self.assertEqual(response.context["order"], field)
            self.assertEqual(response.context["asc"], asc)

    def test_get_ordering_ascending(self) -> None:
        """WorkersListView.get_ordering return correct ordering: "name"."""
        request = self.factory.get(
            "/-/status/workers/", {"order": "name", "asc": "1"}
        )

        view = WorkersListView()
        view.request = request

        self.assertEqual(view.get_ordering(), ["name"])

    def test_get_ordering_descending(self) -> None:
        """WorkersListView.get_ordering return correct ordering: "-name"."""
        request = self.factory.get(
            "/-/status/workers/", {"order": "name", "asc": "0"}
        )

        view = WorkersListView()
        view.request = request

        self.assertEqual(view.get_ordering(), ["-name"])

    def test_get_ordering_not_in_list(self) -> None:
        """WorkersListView.get_ordering return correct: "-name"."""
        request = self.factory.get(
            "/-/status/workers/", {"order": "a_field", "asc": "1"}
        )

        view = WorkersListView()
        view.request = request

        self.assertEqual(view.get_ordering(), ["name"])
