# Copyright © Michal Čihař <michal@weblate.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

import os.path
import tempfile
from difflib import get_close_matches
from itertools import chain
from shutil import copyfile
from unittest import SkipTest

from django.core.files import File
from django.urls import reverse
from django.utils import timezone
from PIL import Image
from rest_framework.test import APITestCase

import weblate.screenshots.views
from weblate.screenshots.models import Screenshot
from weblate.screenshots.views import PyTessBaseAPI, ocr_get_strings
from weblate.trans.tests.test_models import RepoTestCase
from weblate.trans.tests.test_views import FixtureTestCase
from weblate.trans.tests.utils import create_test_user, get_test_file
from weblate.utils.db import using_postgresql
from weblate.utils.locale import c_locale

TEST_SCREENSHOT = get_test_file("screenshot.png")


class ViewTest(FixtureTestCase):
    @classmethod
    def _databases_support_transactions(cls):
        # This is workaround for MySQL as FULL TEXT index does not work
        # well inside a transaction, so we avoid using transactions for
        # tests. Otherwise we end up with no matches for the query.
        # See https://dev.mysql.com/doc/refman/5.6/en/innodb-fulltext-index.html
        if not using_postgresql():
            return False
        return super()._databases_support_transactions()

    def test_list_empty(self):
        response = self.client.get(reverse("screenshots", kwargs=self.kw_component))
        self.assertContains(response, "Screenshots")

    def do_upload(self, **kwargs):
        with open(TEST_SCREENSHOT, "rb") as handle:
            data = {
                "image": handle,
                "name": "Obrazek",
                "translation": self.component.source_translation.pk,
            }
            data.update(kwargs)
            return self.client.post(
                reverse("screenshots", kwargs=self.kw_component), data, follow=True
            )

    def test_upload_denied(self):
        response = self.do_upload()
        self.assertEqual(response.status_code, 403)

    def test_upload(self):
        self.make_manager()
        response = self.do_upload()
        self.assertContains(response, "Obrazek")
        self.assertEqual(Screenshot.objects.count(), 1)

    def test_upload_fail(self):
        self.make_manager()
        response = self.do_upload(name="")
        self.assertContains(response, "Failed to upload screenshot")
        response = self.do_upload(image="")
        self.assertContains(response, "Failed to upload screenshot")

    def test_upload_source(self):
        self.make_manager()
        source = self.component.source_translation.unit_set.all()[0]
        response = self.do_upload(source=source.pk)
        self.assertContains(response, "Obrazek")
        self.assertEqual(Screenshot.objects.count(), 1)
        screenshot = Screenshot.objects.all()[0]
        self.assertEqual(screenshot.name, "Obrazek")
        self.assertEqual(screenshot.units.count(), 1)

    def test_upload_source_invalid(self):
        self.make_manager()
        response = self.do_upload(source="wrong")
        self.assertContains(response, "Obrazek")

    def test_edit(self):
        self.make_manager()
        self.do_upload()
        screenshot = Screenshot.objects.all()[0]
        response = self.client.post(
            screenshot.get_absolute_url(), {"name": "Picture"}, follow=True
        )
        self.assertContains(response, "Picture")
        self.assertEqual(Screenshot.objects.all()[0].name, "Picture")

    def test_delete(self):
        self.make_manager()
        self.do_upload()
        screenshot = Screenshot.objects.all()[0]
        self.client.post(reverse("screenshot-delete", kwargs={"pk": screenshot.pk}))
        self.assertEqual(Screenshot.objects.count(), 0)

    def extract_pk(self, data):
        return int(data.split('data-pk="')[1].split('"')[0])

    def test_source_manipulations(self):
        self.make_manager()
        self.do_upload()
        screenshot = Screenshot.objects.all()[0]

        # Search for string
        response = self.client.post(
            reverse("screenshot-js-search", kwargs={"pk": screenshot.pk}),
            {"q": "hello"},
        )
        data = response.json()
        self.assertEqual(data["responseCode"], 200)
        self.assertIn('<a class="add-string', data["results"])

        source_pk = self.extract_pk(data["results"])

        self.assertEqual(
            source_pk,
            self.component.source_translation.unit_set.search("hello").get().pk,
        )

        # Add found string
        response = self.client.post(
            reverse("screenshot-js-add", kwargs={"pk": screenshot.pk}),
            {"source": source_pk},
        )
        data = response.json()
        self.assertEqual(data["responseCode"], 200)
        self.assertEqual(data["status"], True)
        self.assertEqual(screenshot.units.count(), 1)

        # Updated listing
        response = self.client.get(
            reverse("screenshot-js-get", kwargs={"pk": screenshot.pk})
        )
        self.assertContains(response, "Hello")

        # Remove added string
        self.client.post(
            reverse("screenshot-remove-source", kwargs={"pk": screenshot.pk}),
            {"source": source_pk},
        )
        self.assertEqual(screenshot.units.count(), 0)

    def test_ocr_backend(self):
        if not weblate.screenshots.views.HAS_OCR:
            raise SkipTest("OCR not supported")

        # Convert to greyscale
        image = Image.open(TEST_SCREENSHOT).convert("L")

        # Extract strings
        with c_locale(), PyTessBaseAPI() as api:
            result = list(ocr_get_strings(api, image))

        # Reverse logic would make sense here, but we want to use same order as in views.py
        matches = list(
            chain.from_iterable(
                get_close_matches(part, ["Hello, world!\n"], cutoff=0.9)
                for part in result
            )
        )

        self.assertTrue(
            matches, f"Failed to find string in tesseract results: {result}"
        )

    def test_ocr(self):
        if not weblate.screenshots.views.HAS_OCR:
            raise SkipTest("OCR not supported")
        self.make_manager()
        self.do_upload()
        screenshot = Screenshot.objects.all()[0]

        # Search for string
        response = self.client.post(
            reverse("screenshot-js-ocr", kwargs={"pk": screenshot.pk})
        )
        data = response.json()

        self.assertEqual(data["responseCode"], 200)
        # We should find at least one string
        self.assertIn(
            '<a class="add-string',
            data["results"],
            "OCR recognition not working, no recognized strings found",
        )

    def test_ocr_disabled(self):
        orig = weblate.screenshots.views.HAS_OCR
        weblate.screenshots.views.HAS_OCR = False
        try:
            self.make_manager()
            self.do_upload()
            screenshot = Screenshot.objects.all()[0]

            # Search for string
            response = self.client.post(
                reverse("screenshot-js-ocr", kwargs={"pk": screenshot.pk})
            )
            data = response.json()

            self.assertEqual(data["responseCode"], 500)
        finally:
            weblate.screenshots.views.HAS_OCR = orig

    def test_translation_manipulations(self):
        self.make_manager()
        translation = self.component.translation_set.get(language_code="cs")
        self.do_upload(translation=translation.pk)
        screenshot = Screenshot.objects.all()[0]

        # Search for string
        response = self.client.post(
            reverse("screenshot-js-search", kwargs={"pk": screenshot.pk}),
            {"q": "hello"},
        )
        data = response.json()
        self.assertEqual(data["responseCode"], 200)
        self.assertIn('<a class="add-string', data["results"])

        source_pk = self.extract_pk(data["results"])
        self.assertEqual(source_pk, translation.unit_set.search("hello").get().pk)

        # Add found string
        response = self.client.post(
            reverse("screenshot-js-add", kwargs={"pk": screenshot.pk}),
            {"source": source_pk},
        )
        data = response.json()
        self.assertEqual(data["responseCode"], 200)
        self.assertEqual(data["status"], True)
        self.assertEqual(screenshot.units.count(), 1)

        # Updated listing
        response = self.client.get(
            reverse("screenshot-js-get", kwargs={"pk": screenshot.pk})
        )
        self.assertContains(response, "Hello")

        # Remove added string
        self.client.post(
            reverse("screenshot-remove-source", kwargs={"pk": screenshot.pk}),
            {"source": source_pk},
        )
        self.assertEqual(screenshot.units.count(), 0)


class ScreenshotVCSTest(APITestCase, RepoTestCase):
    """Test class for syncing vcs screenshots in weblate."""

    def setUp(self):
        super().setUp()
        self.user = create_test_user()
        self.user.is_superuser = True
        self.user.save()
        self.client.credentials(HTTP_AUTHORIZATION="Token " + self.user.auth_token.key)
        self.client.login(username="testuser", password="testpassword")

        self.component = self._create_component(
            "json",
            "intermediate/*.json",
            screenshot_filemask="*.png",
        )

        # Add a screenshot linked to the component
        shot = Screenshot.objects.create(
            name="test-update",
            translation=self.component.source_translation,
            repository_filename="test-update.png",
        )
        with open(TEST_SCREENSHOT, "rb") as handle:
            data = handle.read()
            half_data_size = len(data) // 2
            with tempfile.NamedTemporaryFile(suffix="png") as temp_file:
                temp_file.write(data[:half_data_size])
                temp_file.flush()
                temp_file.seek(0)
                shot.image.save("test-update", File(temp_file))

    def test_update_screenshots_from_repo(self):
        repository = self.component.repository
        last_revision = repository.last_revision
        existing_ss_size = Screenshot.objects.filter(
            translation__component=self.component,
            repository_filename="test-update.png",
        )[0].image.size

        copyfile(TEST_SCREENSHOT, os.path.join(repository.path, "test-update.png"))
        with repository.lock:
            repository.set_committer("Second Bar", "second@example.net")
            filenames = ["test-update.png"]
            repository.commit(
                "Test commit", "Foo Bar <foo@bar.com>", timezone.now(), filenames
            )
            self.component.trigger_post_update(last_revision, skip_push=True)

        # Verify that screenshot has been updated after the signal.
        self.assertEqual(
            Screenshot.objects.filter(
                translation__component=self.component,
                repository_filename="test-update.png",
            ).count(),
            1,
        )
        updated_ss_size = Screenshot.objects.filter(
            translation__component=self.component,
            repository_filename="test-update.png",
        )[0].image.size
        self.assertNotEqual(existing_ss_size, updated_ss_size)

    def test_add_screenshots_from_repo(self):
        repository = self.component.repository
        last_revision = repository.last_revision

        copyfile(TEST_SCREENSHOT, os.path.join(repository.path, "test.png"))
        with repository.lock:
            repository.set_committer("Second Bar", "second@example.net")
            filenames = ["test.png"]
            repository.commit(
                "Test commit", "Foo Bar <foo@bar.com>", timezone.now(), filenames
            )
            self.component.trigger_post_update(last_revision, skip_push=True)

        # Verify that screenshot has been added after the signal.
        self.assertEqual(
            Screenshot.objects.filter(
                translation__component=self.component,
                repository_filename="test.png",
            ).count(),
            1,
        )
