import base64
from contextlib import closing
import json
import os
from pathlib import Path
import sqlite3
import subprocess
import tempfile
import unittest

from backup_core import BackupJob
from test_backup import FakeStack, RDB, SQL
from transport import DockerRepository, OperationsError


class NativeRepository(DockerRepository):
    def __init__(self, root, capture, binary):
        self.binary = str(binary)
        self.root = Path(root)
        self.repository = self.root / "repository"
        self.password = self.root / "fixture-password"
        self.password.write_text("synthetic-restic-password-" + "x" * 40)
        self.password.chmod(0o600)
        self.roots = {"capture": str(capture), "uploads": str(self.root / "uploads"), "keys": str(self.root / "keys"),
                      "configuration": str(self.root / "configuration"), "redis": str(self.root / "redis/dump.rdb")}
        for name in ["uploads", "keys", "configuration", "redis"]:
            (self.root / name).mkdir(mode=0o700)
        self.key = "base64:" + base64.b64encode(b"q" * 32).decode()
        (self.root / "keys/app_key").write_text(self.key)
        (self.root / "keys/backup_password").write_text("synthetic-repository-key-must-not-be-captured")
        (self.root / "keys/backup_s3_credentials").write_text("synthetic-transport-credentials-must-not-be-captured")
        (self.root / "uploads/image.txt").write_text("synthetic-private-upload-91d4c45a")
        (self.root / "configuration/stack.env").write_text("APP_ENV=testing\n")
        (self.root / "redis/dump.rdb").write_bytes(RDB)
        self.call("init")

    def call(self, *arguments):
        environment = {"PATH": os.environ["PATH"], "AWS_SHARED_CREDENTIALS_FILE": "/dev/null", "AWS_CONFIG_FILE": "/dev/null",
                       "AWS_EC2_METADATA_DISABLED": "true"}
        result = subprocess.run([self.binary, "--repo", str(self.repository), "--password-file", str(self.password),
                                 "--cache-dir", str(self.root / "cache"), "--compression", "off", "--json", *arguments],
                                env=environment, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if result.returncode:
            raise OperationsError("Synthetic repository operation failed (exit " + str(result.returncode) + ")")
        return result.stdout

    def restore(self, identity, destination):
        self.call("restore", identity, "--target", str(destination), "--verify", "--overwrite", "never")


class ResticRoundtripTest(unittest.TestCase):
    @unittest.skipUnless(os.environ.get("POST76_TEST_RESTIC_BINARY"), "Native Restic verification needs an explicit test binary")
    def test_encrypted_capture_restore_and_database_import_use_only_synthetic_data(self):
        parent = Path(__file__).resolve().parent.parent / "storage/app/private/backup-check"
        parent.mkdir(parents=True, exist_ok=True)
        with tempfile.TemporaryDirectory(prefix="restic-test-", dir=parent) as temporary:
            root = Path(temporary)
            capture = root / "capture"
            capture.mkdir(mode=0o700)
            stack = FakeStack(root)
            repository = NativeRepository(root, capture, os.environ["POST76_TEST_RESTIC_BINARY"])
            job = BackupJob(stack, repository, root / "workspace")
            identity = job.backup(capture, quiesce=True)
            self.assertEqual([], list(capture.iterdir()))
            data = job.restore(identity, root / "recovery")
            restored_capture = data / str(capture).lstrip("/")
            self.assertEqual(SQL, (restored_capture / "database.sql").read_bytes())
            restored_keys = data / repository.roots["keys"].lstrip("/")
            self.assertEqual(repository.key, (restored_keys / "app_key").read_text())
            self.assertFalse((restored_keys / "backup_password").exists())
            self.assertFalse((restored_keys / "backup_s3_credentials").exists())
            self.assertEqual(RDB, (data / repository.roots["redis"].lstrip("/")).read_bytes())
            with closing(sqlite3.connect(root / "recovered.sqlite")) as database:
                database.executescript((restored_capture / "database.sql").read_text())
                self.assertEqual([(7, "synthetic-author")], database.execute("SELECT id, username FROM member").fetchall())
            for path in repository.repository.rglob("*"):
                if path.is_file():
                    self.assertNotIn(b"synthetic-private-upload-91d4c45a", path.read_bytes())
                    self.assertNotIn(repository.key.encode(), path.read_bytes())
            repository.call("check", "--read-data")
            repository.password.write_text("synthetic-wrong-password")
            with self.assertRaises(OperationsError):
                repository.snapshots(["verified"])
            self.assertTrue(json.loads((root / "recovery/restore-result.json").read_text())["database_verified"])


if __name__ == "__main__":
    unittest.main()
