#
# Copyright © 2012 - 2020 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <https://weblate.org/>
#
# 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/>.
#

import os.path
import shutil
import sys
from datetime import timedelta
from tarfile import TarFile
from tempfile import mkdtemp
from unittest import SkipTest

from celery.contrib.testing.tasks import ping
from celery.result import allow_join_result
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.utils import timezone
from django.utils.functional import cached_property

from weblate.auth.models import User
from weblate.formats.models import FILE_FORMATS
from weblate.trans.models import Component, Project
from weblate.utils.files import remove_readonly
from weblate.vcs.models import VCS_REGISTRY

# Directory holding test data
TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")

REPOWEB_URL = "https://nonexisting.weblate.org/blob/master/{{filename}}#L{{line}}"

TESTPASSWORD = make_password("testpassword")


def wait_for_celery(timeout=10):
    with allow_join_result():
        ping.delay().get(timeout=timeout)


def get_test_file(name):
    """Return filename of test file."""
    return os.path.join(TEST_DATA, name)


def create_test_user():
    return User.objects.create(
        username="testuser",
        email="weblate@example.org",
        password=TESTPASSWORD,
        full_name="Weblate Test",
    )


def create_another_user():
    return User.objects.create(
        username="jane",
        email="jane.doe@example.org",
        password=TESTPASSWORD,
        full_name="Jane Doe",
    )


class RepoTestMixin:
    """Mixin for testing with test repositories."""

    updated_base_repos = set()

    local_repo_path = "local:"

    def optional_extract(self, output, tarname):
        """Extract test repository data if needed.

        Checks whether directory exists or is older than archive.
        """
        tarname = get_test_file(tarname)

        if not os.path.exists(output) or os.path.getmtime(output) < os.path.getmtime(
            tarname
        ):

            # Remove directory if outdated
            if os.path.exists(output):
                shutil.rmtree(output, onerror=remove_readonly)

            # Extract new content
            tar = TarFile(tarname)
            tar.extractall(settings.DATA_DIR)
            tar.close()

            # Update directory timestamp
            os.utime(output, None)
        self.updated_base_repos.add(output)

    @staticmethod
    def get_repo_path(name):
        return os.path.join(settings.DATA_DIR, name)

    @property
    def git_base_repo_path(self):
        path = self.get_repo_path("test-base-repo.git")
        if path not in self.updated_base_repos:
            self.optional_extract(path, "test-base-repo.git.tar")
        return path

    @cached_property
    def git_repo_path(self):
        path = self.get_repo_path("test-repo.git")
        shutil.copytree(self.git_base_repo_path, path)
        return path

    @property
    def mercurial_base_repo_path(self):
        path = self.get_repo_path("test-base-repo.hg")
        if path not in self.updated_base_repos:
            self.optional_extract(path, "test-base-repo.hg.tar")
        return path

    @cached_property
    def mercurial_repo_path(self):
        path = self.get_repo_path("test-repo.hg")
        shutil.copytree(self.mercurial_base_repo_path, path)
        return path

    @property
    def subversion_base_repo_path(self):
        path = self.get_repo_path("test-base-repo.svn")
        if path not in self.updated_base_repos:
            self.optional_extract(path, "test-base-repo.svn.tar")
        return path

    @cached_property
    def subversion_repo_path(self):
        path = self.get_repo_path("test-repo.svn")
        shutil.copytree(self.subversion_base_repo_path, path)
        return path

    def clone_test_repos(self):
        dirs = ["test-repo.git", "test-repo.hg", "test-repo.svn"]
        # Remove possibly existing directories
        for name in dirs:
            path = self.get_repo_path(name)
            if os.path.exists(path):
                shutil.rmtree(path, onerror=remove_readonly)

        # Remove cached paths
        keys = ["git_repo_path", "mercurial_repo_path", "subversion_repo_path"]
        for key in keys:
            if key in self.__dict__:
                del self.__dict__[key]

        # Remove possibly existing project directory
        test_repo_path = os.path.join(settings.DATA_DIR, "vcs", "test")
        if os.path.exists(test_repo_path):
            shutil.rmtree(test_repo_path, onerror=remove_readonly)

    def create_project(self, **kwargs):
        """Create test project."""
        project = Project.objects.create(
            name="Test", slug="test", web="https://nonexisting.weblate.org/", **kwargs
        )
        self.addCleanup(shutil.rmtree, project.full_path, True)
        return project

    def format_local_path(self, path):
        """Format path for local access to the repository."""
        if sys.platform != "win32":
            return "file://{}".format(path)
        return "file:///{}".format(path.replace("\\", "/"))

    def _create_component(
        self,
        file_format,
        mask,
        template="",
        new_base="",
        vcs="git",
        branch=None,
        **kwargs
    ):
        """Create real test component."""
        if file_format not in FILE_FORMATS:
            raise SkipTest("File format {0} is not supported!".format(file_format))
        if "project" not in kwargs:
            kwargs["project"] = self.create_project()

        repo = push = self.format_local_path(getattr(self, "{0}_repo_path".format(vcs)))
        if vcs not in VCS_REGISTRY:
            raise SkipTest("VCS {0} not available!".format(vcs))

        if "new_lang" not in kwargs:
            kwargs["new_lang"] = "contact"

        if "push_on_commit" not in kwargs:
            kwargs["push_on_commit"] = False

        if "name" not in kwargs:
            kwargs["name"] = "Test"
        kwargs["slug"] = kwargs["name"].lower()

        if branch is None:
            branch = VCS_REGISTRY[vcs].default_branch

        return Component.objects.create(
            repo=repo,
            push=push,
            branch=branch,
            filemask=mask,
            template=template,
            file_format=file_format,
            repoweb=REPOWEB_URL,
            new_base=new_base,
            vcs=vcs,
            **kwargs
        )

    def create_component(self):
        """Wrapper method for providing test component."""
        return self.create_po()

    def create_po(self, **kwargs):
        return self._create_component("po", "po/*.po", **kwargs)

    def create_po_branch(self):
        return self._create_component("po", "translations/*.po", branch="translations")

    def create_po_push(self):
        return self.create_po(push_on_commit=True)

    def create_po_empty(self):
        return self._create_component(
            "po", "po-empty/*.po", new_base="po-empty/hello.pot", new_lang="add"
        )

    def create_po_mercurial(self):
        return self.create_po(vcs="mercurial")

    def create_po_mercurial_branch(self):
        return self._create_component(
            "po", "translations/*.po", branch="translations", vcs="mercurial"
        )

    def create_po_svn(self):
        return self.create_po(vcs="subversion")

    def create_po_new_base(self, **kwargs):
        return self.create_po(new_base="po/hello.pot", **kwargs)

    def create_po_link(self):
        return self._create_component("po", "po-link/*.po")

    def create_po_mono(self):
        return self._create_component("po-mono", "po-mono/*.po", "po-mono/en.po")

    def create_srt(self):
        return self._create_component("srt", "srt/*.srt", "srt/en.srt")

    def create_ts(self, suffix="", **kwargs):
        return self._create_component("ts", "ts{0}/*.ts".format(suffix), **kwargs)

    def create_ts_mono(self):
        return self._create_component("ts", "ts-mono/*.ts", "ts-mono/en.ts")

    def create_iphone(self, **kwargs):
        return self._create_component(
            "strings", "iphone/*.lproj/Localizable.strings", **kwargs
        )

    def create_android(self, suffix="", **kwargs):
        return self._create_component(
            "aresource",
            "android{}/values-*/strings.xml".format(suffix),
            "android{}/values/strings.xml".format(suffix),
            **kwargs
        )

    def create_json(self):
        return self._create_component("json", "json/*.json")

    def create_json_mono(self, suffix="mono", **kwargs):
        return self._create_component(
            "json",
            "json-{}/*.json".format(suffix),
            "json-{}/en.json".format(suffix),
            **kwargs
        )

    def create_json_webextension(self):
        return self._create_component(
            "webextension",
            "webextension/_locales/*/messages.json",
            "webextension/_locales/en/messages.json",
        )

    def create_json_intermediate(self, **kwargs):
        return self._create_component(
            "json",
            "intermediate/*.json",
            "intermediate/en.json",
            intermediate="intermediate/dev.json",
            **kwargs
        )

    def create_json_intermediate_empty(self, **kwargs):
        return self._create_component(
            "json",
            "intermediate/lang-*.json",
            "intermediate/lang-en.json",
            intermediate="intermediate/dev.json",
            **kwargs
        )

    def create_joomla(self):
        return self._create_component("joomla", "joomla/*.ini", "joomla/en-GB.ini")

    def create_tsv(self):
        return self._create_component("csv", "tsv/*.txt")

    def create_csv(self):
        return self._create_component("csv", "csv/*.txt")

    def create_csv_mono(self):
        return self._create_component("csv", "csv-mono/*.csv", "csv-mono/en.csv")

    def create_php_mono(self):
        return self._create_component("php", "php-mono/*.php", "php-mono/en.php")

    def create_java(self):
        return self._create_component(
            "properties",
            "java/swing_messages_*.properties",
            "java/swing_messages.properties",
        )

    def create_xliff(self, name="default", **kwargs):
        return self._create_component("xliff", "xliff/*/{0}.xlf".format(name), **kwargs)

    def create_xliff_mono(self):
        return self._create_component("xliff", "xliff-mono/*.xlf", "xliff-mono/en.xlf")

    def create_resx(self):
        return self._create_component("resx", "resx/*.resx", "resx/en.resx")

    def create_yaml(self):
        return self._create_component("yaml", "yml/*.yml", "yml/en.yml")

    def create_ruby_yaml(self):
        return self._create_component("ruby-yaml", "ruby-yml/*.yml", "ruby-yml/en.yml")

    def create_dtd(self):
        return self._create_component("dtd", "dtd/*.dtd", "dtd/en.dtd")

    def create_appstore(self):
        return self._create_component("appstore", "metadata/*", "metadata/en-US")

    def create_link(self, **kwargs):
        parent = self.create_iphone(*kwargs)
        return Component.objects.create(
            name="Test2",
            slug="test2",
            project=parent.project,
            repo="weblate://test/test",
            file_format="po",
            filemask="po/*.po",
            new_lang="contact",
        )

    def create_link_existing(self):
        return Component.objects.create(
            name="Test2",
            slug="test2",
            project=self.project,
            repo="weblate://test/test",
            file_format="po",
            filemask="po-duplicates/*.po",
            new_lang="contact",
        )


class TempDirMixin:
    tempdir = None

    def create_temp(self):
        self.tempdir = mkdtemp(suffix="weblate")

    def remove_temp(self):
        if self.tempdir:
            shutil.rmtree(self.tempdir, onerror=remove_readonly)
            self.tempdir = None


def create_billing(user):
    from weblate.billing.models import Billing, Invoice, Plan

    plan = Plan.objects.create(
        display_limit_projects=1, name="Basic plan", price=19, yearly_price=199
    )
    billing = Billing.objects.create(plan=plan)
    billing.owners.add(user)
    Invoice.objects.create(
        billing=billing,
        amount=19,
        start=timezone.now() - timedelta(days=1),
        end=timezone.now() + timedelta(days=1),
    )
    return billing
