# 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 for task status view."""

from typing import override

from django.template.loader import get_template
from django.test import RequestFactory, override_settings
from django.urls import reverse

import debusine.worker.tags as wtags
from debusine.db.context import context
from debusine.db.models import WorkRequest
from debusine.db.playground import scenarios
from debusine.server.status import TaskQueueSummary, WorkerSummary
from debusine.tasks.models import WorkerType
from debusine.test.django import TestCase
from debusine.web.views.places import IndexPlace
from debusine.web.views.task_status import TaskStatusView
from debusine.web.views.tests.utils import ViewTestMixin


class TaskStatusViewTests(ViewTestMixin, TestCase):
    """Tests for TaskStatusView."""

    scenario = scenarios.DefaultContext(set_current=True)

    @override
    def setUp(self) -> None:
        super().setUp()
        self.factory = RequestFactory()

    def test_get_place(self) -> None:
        view = TaskStatusView()
        view.request = RequestFactory().get(reverse("task-status"))
        place = view.get_place()
        self.assertIsInstance(place, IndexPlace)
        self.assertEqual(place.title, "Task queue")
        self.assertEqual(place.url, reverse("task-status"))
        self.assertIsNone(place.icon)
        self.assertIsNone(place.description)

    def test_template_no_work_requests(self) -> None:
        """Test template display the "No work requests..." message."""
        response = self.client.get(reverse("task-status"))

        self.assertContains(
            response, "<p>There are no pending work requests.</p>", html=True
        )

    def test_template_simple(self) -> None:
        """Test template row for arch-None without any tasks."""
        self.playground.create_worker(
            worker_type=WorkerType.EXTERNAL,
            extra_dynamic_metadata={
                "system:native_architecture": "amd64",
                "system:architectures": ["amd64", "i386"],
            },
        ).mark_connected()
        response = self.client.get(reverse("task-status"))
        tree = self.assertResponseHTML(response)
        arch_table = self.assertHasElement(
            tree, "//table[@id='task-arch-queue']"
        )
        self.assertHasElement(arch_table.tbody, "tr[@id='arch-_none_']")

    @override_settings(DISABLE_AUTOMATIC_SCHEDULING=True)
    def test_template(self) -> None:
        """Test template contains expected output."""
        source = self.playground.create_minimal_source_package_artifact()
        environment = self.playground.create_debian_environment().artifact
        assert environment is not None

        worker = self.playground.create_worker(
            worker_type=WorkerType.EXTERNAL,
            extra_dynamic_metadata={
                "system:native_architecture": "amd64",
                "system:architectures": ["amd64", "i386"],
            },
        )
        worker.mark_connected()

        # One pending amd64 task with a worker assigned, but not yet running
        task_amd64 = self.playground.create_sbuild_work_request(
            source=source, environment=environment, architecture="amd64"
        )
        self.playground.advance_work_request(task_amd64, worker=worker)

        # Create a task that is (oddly) running without an assigned worker.
        unassigned_wr = self.playground.create_sbuild_work_request(
            source=source, environment=environment, architecture="i386"
        )
        self.playground.advance_work_request(unassigned_wr, mark_pending=True)
        unassigned_wr.status = WorkRequest.Statuses.RUNNING
        unassigned_wr.save()

        # Noop task currently uses the fallback architecture
        self.playground.create_worker_task(task_name="noop", mark_pending=True)

        # Task that successfully transitioned to pending but now fails to
        # validate
        corrupted_wr = self.playground.create_worker_task(
            task_name="sbuild",
            mark_pending=True,
            validate=False,
            use_data=False,
        )
        corrupted_wr.scheduler_tags_required = [
            wtags.BUILD_ARCH_PREFIX + "amd64"
        ]
        corrupted_wr.save()

        response = self.client.get(reverse("task-status"))
        tree = self.assertResponseHTML(response)

        type_table = self.assertHasElement(tree, "//table[@id='task-queue']")
        row = self.assertHasElement(
            type_table.tbody, "tr[@id='worker_type-external']"
        )
        # Columns: type, registered, connected, busy, idle, running, pending
        self.assertHTMLContentsEquivalent(row.td[0], "<td>External</td>")
        self.assertHTMLContentsEquivalent(row.td[1], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[2], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[3], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[4], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[5], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[6], "<td>3</td>")

        arch_table = self.assertHasElement(
            tree, "//table[@id='task-arch-queue']"
        )

        # First row: architecture amd64, 1 connected worker, 2 work requests
        row = self.assertHasElement(arch_table.tbody, "tr[@id='arch-amd64']")
        self.assertHTMLContentsEquivalent(row.td[0], '<td>amd64</td>')
        # Columns: registered, connected, busy, idle, running, pending
        self.assertHTMLContentsEquivalent(row.td[1], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[2], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[3], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[4], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[5], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[6], "<td>2</td>")

        # Second row: architecture i386, 1 connected worker, 0 work requests
        row = arch_table.tbody.xpath("tr[@id='arch-i386']")[0]
        self.assertHTMLContentsEquivalent(row.td[0], '<td>i386</td>')
        self.assertHTMLContentsEquivalent(row.td[1], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[2], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[3], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[4], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[5], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[6], "<td>0</td>")

        # Third row: arch unspecified, 1 connected worker, 2 work requests
        row = arch_table.tbody.xpath("tr[@id='arch-_none_']")[0]
        self.assertHTMLContentsEquivalent(row.td[0], "<td>Not Specified</td>")
        self.assertHTMLContentsEquivalent(row.td[1], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[2], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[3], "<td>1</td>")
        self.assertHTMLContentsEquivalent(row.td[4], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[5], "<td>0</td>")
        self.assertHTMLContentsEquivalent(row.td[6], "<td>1</td>")

        # Three rows for two architectures: amd64 and i386, then None
        self.assertEqual(len(arch_table.tbody.tr), 3)

    def test_task_queue_sorted_in_template(self) -> None:
        """task_queue is sorted by architecture in the template."""
        worker_status = {
            WorkerType.EXTERNAL: {
                "worker_tasks": TaskQueueSummary(),
                "workers": WorkerSummary(),
            },
        }
        # Create an unsorted arch_status dictionary
        arch_status = {
            "i386": {
                "external_workers_arch": WorkerSummary(connected=3),
                "worker_tasks_arch": TaskQueueSummary(pending=5),
            },
            "amd64": {
                "external_workers_arch": WorkerSummary(connected=2),
                "worker_tasks_arch": TaskQueueSummary(pending=3),
            },
        }
        template = get_template("web/task_status.html")
        # FIXME: template.render does not use context processors, so we need to
        # explicitly re-add here what the template expects. This may get more
        # complicated in the future, as the base template may get to rely more
        # on default context
        rendered_html = template.render(
            {
                "arch_status": arch_status,
                "worker_status": worker_status,
                "workspace": context.workspace,
                "base_template": "web/_base_debusine.html",
            }
        )

        i386_index = rendered_html.find("i386")
        amd64_index = rendered_html.find("amd64")

        self.assertLess(amd64_index, i386_index)
