/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.rollback;

import static com.google.common.truth.Truth.assertThat;

import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.util.IntArray;
import android.util.SparseLongArray;

import com.google.common.truth.Correspondence;

import org.json.JSONObject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.File;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@RunWith(JUnit4.class)
public class RollbackStoreTest {

    private static final int ID = 123;
    private static final int USER = 0;
    private static final String INSTALLER = "some.installer";

    private static final Correspondence<VersionedPackage, VersionedPackage> VER_PKG_CORR =
            new Correspondence<VersionedPackage, VersionedPackage>() {
                @Override
                public boolean compare(VersionedPackage a, VersionedPackage b) {
                    if (a == null || b == null) {
                        return a == b;
                    }
                    return a.equals(b);
                }

                @Override
                public String toString() {
                    return "is the same as";
                }
            };

    private static final Correspondence<PackageRollbackInfo.RestoreInfo,
            PackageRollbackInfo.RestoreInfo>
            RESTORE_INFO_CORR =
            new Correspondence<PackageRollbackInfo.RestoreInfo, PackageRollbackInfo.RestoreInfo>() {
                @Override
                public boolean compare(PackageRollbackInfo.RestoreInfo a,
                        PackageRollbackInfo.RestoreInfo b) {
                    if (a == null || b == null) {
                        return a == b;
                    }
                    return a.userId == b.userId
                            && a.appId == b.appId
                            && Objects.equals(a.seInfo, b.seInfo);
                }

                @Override
                public String toString() {
                    return "is the same as";
                }
            };

    private static final String JSON_ROLLBACK = "{'info':{'rollbackId':123,'packages':"
            + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
            + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
            + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
            + "'installedUsers':"
            + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
            + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
            + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
            + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
            + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
            + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
            + "'installedUsers':[55,79],"
            + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
            + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
            + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
            + "'stagedSessionId':-1,'state':'enabling','apkSessionId':-1,"
            + "'restoreUserDataInProgress':true, 'userId':0,"
            + "'installerPackageName':'some.installer'}";

    @Rule
    public TemporaryFolder mFolder = new TemporaryFolder();

    private File mRollbackDir;

    private RollbackStore mRollbackStore;

    @Before
    public void setUp() throws Exception {
        mRollbackStore = new RollbackStore(mFolder.getRoot());
        mRollbackDir = mFolder.newFolder(ID + "");
        mFolder.newFile("rollback.json");
    }

    @Test
    public void createNonStaged() {
        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);

        assertThat(rollback.getBackupDir().getAbsolutePath())
                .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);

        assertThat(rollback.isStaged()).isFalse();
        assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
        assertThat(rollback.info.getPackages()).isEmpty();
        assertThat(rollback.isEnabling()).isTrue();
    }

    @Test
    public void createStaged() {
        Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER, null);

        assertThat(rollback.getBackupDir().getAbsolutePath())
                .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);

        assertThat(rollback.isStaged()).isTrue();
        assertThat(rollback.getStagedSessionId()).isEqualTo(897);

        assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
        assertThat(rollback.info.getPackages()).isEmpty();
        assertThat(rollback.isEnabling()).isTrue();
    }

    @Test
    public void saveAndLoadRollback() {
        Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);

        origRb.setRestoreUserDataInProgress(true);
        origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
        origRb.info.getCausePackages().add(new VersionedPackage("com.pack.age", 99));
        origRb.info.setCommittedSessionId(123456);

        PackageRollbackInfo pkgInfo1 =
                new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
                        new VersionedPackage("com.something.else", 5), new IntArray(),
                        new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
        pkgInfo1.getPendingBackups().add(8);
        pkgInfo1.getPendingBackups().add(888);
        pkgInfo1.getPendingBackups().add(88885);
        pkgInfo1.getCeSnapshotInodes().put(12, 424);
        pkgInfo1.getCeSnapshotInodes().put(222772, 10000000000L);
        pkgInfo1.getCeSnapshotInodes().put(10, -67);

        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(4980, 3442322, "seInfo"));
        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(-89, 15, "otherSeInfo"));

        pkgInfo1.getSnapshottedUsers().add(11);
        pkgInfo1.getSnapshottedUsers().add(1);
        pkgInfo1.getSnapshottedUsers().add(0);

        PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
                new VersionedPackage("another.package", 2),
                new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
                false, false, new IntArray(), new SparseLongArray());
        pkgInfo2.getPendingBackups().add(57);

        pkgInfo2.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(180, -120, ""));

        origRb.info.getPackages().add(pkgInfo1);
        origRb.info.getPackages().add(pkgInfo2);

        RollbackStore.saveRollback(origRb);

        List<Rollback> loadedRollbacks = mRollbackStore.loadRollbacks();
        assertThat(loadedRollbacks).hasSize(1);
        Rollback loadedRb = loadedRollbacks.get(0);

        assertRollbacksAreEquivalent(loadedRb, origRb);
    }

    @Test
    public void loadFromJson() throws Exception {
        Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);

        expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z"));
        expectedRb.setRestoreUserDataInProgress(true);
        expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23));
        expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999));
        expectedRb.info.setCommittedSessionId(45654465);

        PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
                new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
                false, false, new IntArray(), new SparseLongArray());
        pkgInfo1.getPendingBackups().add(59);
        pkgInfo1.getPendingBackups().add(1245);
        pkgInfo1.getPendingBackups().add(124544);
        pkgInfo1.getCeSnapshotInodes().put(546546, 345689375);
        pkgInfo1.getCeSnapshotInodes().put(2222, 81641654445L);
        pkgInfo1.getCeSnapshotInodes().put(1, -6);

        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(498, 32322, "wombles"));
        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(-895, 1, "pingu"));

        pkgInfo1.getSnapshottedUsers().add(498468432);
        pkgInfo1.getSnapshottedUsers().add(1111);
        pkgInfo1.getSnapshottedUsers().add(98464);

        PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
                new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
                false, false, new IntArray(), new SparseLongArray());
        pkgInfo2.getPendingBackups().add(5);

        pkgInfo2.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(18, -12, ""));

        pkgInfo2.getSnapshottedUsers().add(55);
        pkgInfo2.getSnapshottedUsers().add(79);

        expectedRb.info.getPackages().add(pkgInfo1);
        expectedRb.info.getPackages().add(pkgInfo2);

        Rollback parsedRb = RollbackStore.rollbackFromJson(
                new JSONObject(JSON_ROLLBACK), expectedRb.getBackupDir());

        assertRollbacksAreEquivalent(parsedRb, expectedRb);
    }

    @Test
    public void saveAndDelete() {
        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);

        RollbackStore.saveRollback(rollback);

        File expectedFile = new File(mRollbackDir.getAbsolutePath() + "/rollback.json");

        assertThat(expectedFile.exists()).isTrue();

        RollbackStore.deleteRollback(rollback);

        assertThat(expectedFile.exists()).isFalse();
    }

    private void assertRollbacksAreEquivalent(Rollback b, Rollback a) {
        assertThat(b.info.getRollbackId()).isEqualTo(ID);

        assertThat(b.getBackupDir()).isEqualTo(a.getBackupDir());

        assertThat(b.isRestoreUserDataInProgress())
                .isEqualTo(a.isRestoreUserDataInProgress());

        assertThat(b.getTimestamp()).isEqualTo(a.getTimestamp());

        assertThat(b.isEnabling()).isEqualTo(a.isEnabling());
        assertThat(b.isAvailable()).isEqualTo(a.isAvailable());
        assertThat(b.isCommitted()).isEqualTo(a.isCommitted());

        assertThat(b.isStaged()).isEqualTo(a.isStaged());

        assertThat(b.getApexPackageNames())
                .containsExactlyElementsIn(a.getApexPackageNames());

        assertThat(b.getStagedSessionId()).isEqualTo(a.getStagedSessionId());

        assertThat(b.info.getCommittedSessionId()).isEqualTo(a.info.getCommittedSessionId());

        assertThat(b.info.getCausePackages()).comparingElementsUsing(VER_PKG_CORR)
                .containsExactlyElementsIn(a.info.getCausePackages());

        assertThat(b.info.getPackages()).hasSize(a.info.getPackages().size());

        for (int i = 0; i < b.info.getPackages().size(); i++) {
            assertPackageRollbacksAreEquivalent(
                    b.info.getPackages().get(i), a.info.getPackages().get(i));
        }

        assertThat(a.getUserId()).isEqualTo(b.getUserId());
        assertThat(a.getInstallerPackageName()).isEqualTo(b.getInstallerPackageName());
    }

    private void assertPackageRollbacksAreEquivalent(PackageRollbackInfo b, PackageRollbackInfo a) {
        assertThat(b.getPackageName()).isEqualTo(a.getPackageName());

        assertThat(b.getVersionRolledBackFrom()).isEqualTo(a.getVersionRolledBackFrom());
        assertThat(b.getVersionRolledBackTo()).isEqualTo(a.getVersionRolledBackTo());

        assertThat(b.getPendingBackups().toArray()).isEqualTo(a.getPendingBackups().toArray());

        assertThat(b.getPendingRestores()).comparingElementsUsing(RESTORE_INFO_CORR)
                .containsExactlyElementsIn(a.getPendingRestores());

        assertThat(b.isApex()).isEqualTo(a.isApex());

        assertThat(b.getSnapshottedUsers().toArray()).isEqualTo(a.getSnapshottedUsers().toArray());

        assertThat(b.getCeSnapshotInodes().toString())
                .isEqualTo(a.getCeSnapshotInodes().toString());
    }

}
