#!/usr/bin/env python3
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Michael Terry

import glob
import os
import stat
import subprocess
from contextlib import contextmanager
from time import sleep
from unittest import expectedFailure, skip

import ddt
from dogtail.predicate import GenericPredicate
from dogtail.rawinput import keyCombo
from gi.repository import GLib

from . import BaseTest, ResticMixin


@ddt.ddt
class BackupTest(BaseTest):
    def setUp(self):
        super().setUp()

        # Set up destination
        self.destdir = self.rootdir + "/dest"
        # os.mkdir(self.destdir)
        self.set_string("backend", "local")
        self.set_string("folder", self.destdir, child="local")

    @property
    def backup_files(self):
        try:
            return list(glob.iglob(self.destdir + "/**/*", recursive=True))
        except FileNotFoundError:
            return []

    @contextmanager
    def new_files(self, makes_new=True):
        initial = self.backup_files
        yield
        if makes_new:
            assert initial != self.backup_files
        else:
            assert initial == self.backup_files

    def assert_encrypted(self, encrypted: bool):
        if self.restic:
            return

        has_gpg = any(x.endswith(".difftar.gpg") for x in self.backup_files)
        has_gz = any(x.endswith(".difftar.gz") for x in self.backup_files)
        if encrypted:
            assert has_gpg and not has_gz, self.backup_files
        else:
            assert has_gz and not has_gpg, self.backup_files

    def test_from_main_window(self):
        app = self.cmd()
        app.childNamed("Create Your First Backup…").click()
        with self.new_files():
            window = self.get_dialog(app)
            window.button("Back Up").click()
            self.walk_initial_backup(app)

        with self.new_files():
            app.button("Back Up Now").click()
            self.walk_incremental_backup(app)

        self.assert_encrypted(False)

    def test_from_commandline(self):
        app = self.cmd("--backup")
        with self.new_files():
            self.walk_initial_backup(app)

        with self.new_files():
            app = self.cmd("--backup")
            self.walk_incremental_backup(app)

        self.assert_encrypted(False)

    def test_from_monitor(self):
        self.set_boolean("periodic", True)

        self.monitor()
        keyCombo("<Super>v")
        sleep(1)
        keyCombo("Enter")
        keyCombo("Enter")
        app = self.get_app()

        # ensure window has focus (different modes either get rid of
        # notification drawer or not...)
        self.get_alert(app).click()

        with self.new_files():
            self.walk_initial_backup(app)

        self.assert_encrypted(False)

    def test_storage_error(self):
        self.addCleanup(os.chmod, self.rootdir, stat.S_IRWXU)
        os.chmod(self.rootdir, stat.S_IRUSR | stat.S_IXUSR)
        app = self.cmd("--backup")
        with self.new_files(False):
            window = self.get_alert(app)
            window.button("Close").click()

    def test_encrypted(self):
        app = self.cmd("--backup")
        with self.new_files():
            self.walk_initial_backup(app, password="t")

        app = self.cmd("--backup")
        # Try once with the wrong password, just for fun
        # with self.new_files(False):
        self.walk_incremental_backup(app, password="nope", wait=False)
        with self.new_files():
            self.walk_incremental_backup(app, password="t")

        self.assert_encrypted(True)

    def test_hostname_question(self):
        app = self.cmd("--backup")
        with self.new_files():
            self.walk_initial_backup(app)

        # Change hostname
        subprocess.run(
            "sed -i 's/Hostname .*/Hostname dejanewhostnametest/' "
            f"{self.destdir}/*.manifest",
            shell=True,
            check=True,
        )
        self.clean_cache()

        app = self.cmd("--backup")
        with self.new_files():
            window = self.get_alert(app)
            window.child(roleName="label", name="Computer Name Changed")
            window.button("Continue").click()
            self.walk_incremental_backup(app)

    @ddt.data(True, False)
    def test_resume(self, initial):
        if not initial:
            app = self.cmd("--backup")
            self.walk_initial_backup(app, password="t")

        last_backup = self.get_string("last-backup")
        last_run = self.get_string("last-run")

        self.randomize_srcdir()
        app = self.cmd("--backup")
        if initial:
            self.walk_initial_backup(app, password="t", wait=False)
        else:
            self.walk_incremental_backup(app, password="t", wait=False)

        def mid_progress():
            bar = app.findChild(
                GenericPredicate(roleName="progress bar"),
                retry=False,
                requireResult=False,
            )
            return bar and bar.value >= 0.3

        with self.new_files():
            self.wait_for(mid_progress)
            sleep(0.2)  # give time for duplicity to write a .part file
            app.button("Pause").click()
            self.wait_for_banner(app, "Pause", check_first=False)

        assert last_backup == self.get_string("last-backup")
        assert last_run != self.get_string("last-run")

        app = self.cmd("--backup")
        self.walk_incremental_backup(app, password="nope", wait=False)
        self.walk_incremental_backup(app, password="t", wait=False)
        self.did_resume = False

        def finish_progress():
            try:
                bar = app.findChild(
                    GenericPredicate(roleName="progress bar"),
                    retry=False,
                    requireResult=False,
                )
                if not bar:
                    return True
                elif bar.value >= 0.3:
                    self.did_resume = True
                elif not self.did_resume:
                    assert bar.value == 0
                return False
            except GLib.GError:
                return True

        with self.new_files():
            self.wait_for(finish_progress, timeout=120)
            self.wait_for_banner(app, "Pause", check_first=False)

    def test_no_passphrase_change_on_full(self):
        """
        Ensure we check passphrases between full backups
        https://bugs.launchpad.net/duplicity/+bug/918489
        """
        self.set_int("full-backup-period", 0)

        app = self.cmd("--backup")
        self.walk_initial_backup(app, password="t")

        app = self.cmd("--backup")
        self.walk_incremental_backup(app, password="nope", wait=False)
        self.walk_incremental_backup(app, password="t")

    def test_nag_check(self):
        app = self.cmd("--backup")
        self.walk_initial_backup(app, password="t", remember=True)

        # One backup just to confirm we don't need password
        app = self.cmd("--backup")
        self.walk_incremental_backup(app, password=None)

        months_ago = GLib.DateTime.new_now_utc().add_days(-60).format_iso8601()
        self.set_string("nag-check", months_ago)

        app = self.cmd("--backup")
        # Wait for prompt (a little longer to appear than normal dogtail timeouts)
        self.wait_for(
            lambda: app.findChild(
                lambda x: x.roleName == "text" and x.name == "Encryption password",
                requireResult=False,
                retry=False,
            )
        )
        self.walk_incremental_backup(app, password="nope", wait=False)
        self.walk_incremental_backup(app, password="t")

        assert self.get_string("nag-check") != months_ago


@ddt.ddt
class ResticBackupTest(ResticMixin, BackupTest):
    @expectedFailure  # it's too fast - need a better test
    @ddt.data(True, False)
    def test_resume(self, initial):
        super().test_resume(initial)

    @skip("hostname question doesn't exist in restic")
    def test_hostname_question(self):
        super().test_hostname_question()


class MainUITest(BaseTest):
    def setUp(self):
        super().setUp()

        # Set up destination
        self.destdir = self.rootdir + "/dest"
        self.set_string("backend", "local")
        self.set_string("folder", self.destdir, child="local")

    def test_dialog_does_not_block_new_backups(self):
        app = self.cmd("--backup")
        self.walk_initial_backup(app, password="t")
        app = self.cmd("--backup")
        # confirm there is now be a password dialog showing
        entry = app.child(roleName="text entry", label="Encryption password")
        app = self.cmd("--backup", "--auto")
        self.wait_for(lambda: entry.dead)
        # new password dialog is waiting
        self.walk_incremental_backup(app, password="t")

