/*
 * Copyright (C) 2017 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.net.watchlist;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;

import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.FileUtils;
import android.os.Looper;
import android.os.UserManager;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistLoggingHandlerTests
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class WatchlistLoggingHandlerTests {

    private static final String APK_A = "A.apk";
    private static final String APK_B = "B.apk";
    private static final String APK_C = "C.apk";
    private static final String APK_A_CONTENT = "AAA";
    private static final String APK_B_CONTENT = "BBB";
    private static final String APK_C_CONTENT = "CCC";
    // Sha256 of "AAA"
    private static final String APK_A_CONTENT_HASH =
            "CB1AD2119D8FAFB69566510EE712661F9F14B83385006EF92AEC47F523A38358";
    // Sha256 of "BBB"
    private static final String APK_B_CONTENT_HASH =
            "DCDB704109A454784B81229D2B05F368692E758BFA33CB61D04C1B93791B0273";

    private Context mServiceContext;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        final Context context = InstrumentationRegistry.getContext();
        final UserManager mockUserManager = mock(UserManager.class);
        final PackageManager mockPackageManager = mock(PackageManager.class);

        // Context that will be used by WatchlistLoggingHandler
        mServiceContext = new ContextWrapper(context) {
            @Override
            public PackageManager getPackageManager() {
                return mockPackageManager;
            }

            @Override
            public Object getSystemService(String name) {
                switch (name) {
                    case Context.USER_SERVICE:
                        return mockUserManager;
                    default:
                        return super.getSystemService(name);
                }
            }
        };

        // Returns 2 users, user 0 and user 10
        doAnswer((InvocationOnMock invocation) -> {
            final ArrayList<UserInfo> info = new ArrayList<>();
            info.add(new UserInfo(0, "user1", 0));
            info.add(new UserInfo(10, "user2", 0));
            return info;
        }).when(mockUserManager).getUsers();

        // Returns 2 apps, with uid 1 and uid 2
        doAnswer((InvocationOnMock invocation) -> {
            final List<ApplicationInfo> result = new ArrayList<>();
            ApplicationInfo info1 = new ApplicationInfo();
            info1.uid = 1;
            result.add(info1);
            ApplicationInfo info2 = new ApplicationInfo();
            info2.uid = 2;
            result.add(info2);
            return result;
        }).when(mockPackageManager).getInstalledApplications(anyInt());

        // Uid 1 app is installed in primary user only and package name is "A"
        // Uid 2 app is installed in both primary user and secondary user, package name is "B"
        // Uid 3 app is installed in secondary user and package name is "C"
        doAnswer((InvocationOnMock invocation) -> {
            int uid = (int) invocation.getArguments()[0];
            if (uid == 1) {
                return new String[]{"A"};
            } else if (uid == 1000001) {
                return null;
            } else if (uid == 2) {
                return new String[]{"B"};
            } else if (uid == 1000002) {
                return new String[]{"B"};
            } else if (uid == 3) {
                return null;
            } else if (uid == 1000002) {
                return new String[]{"C"};
            }
            return null;
        }).when(mockPackageManager).getPackagesForUid(anyInt());

        String fileDir = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath();
        // Simulate app's apk file path
        doAnswer((InvocationOnMock invocation) -> {
            String pkg = (String) invocation.getArguments()[0];
            PackageInfo result = new PackageInfo();
            result.applicationInfo = new ApplicationInfo();
            result.applicationInfo.publicSourceDir = fileDir + "/" + pkg + ".apk";
            return result;
        }).when(mockPackageManager).getPackageInfoAsUser(anyString(), anyInt(), anyInt());

        FileUtils.bytesToFile(fileDir + "/" + APK_A, APK_A_CONTENT.getBytes());
        FileUtils.bytesToFile(fileDir + "/" + APK_B, APK_B_CONTENT.getBytes());
    }

    @After
    public void tearDown() {
        String fileDir = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath();
        new File(fileDir, APK_A).delete();
        new File(fileDir, APK_B).delete();
    }

    @Test
    public void testWatchlistLoggingHandler_getAllDigestsForReportWithMultiUsers()
            throws Exception {
        List<String> result = new WatchlistLoggingHandler(mServiceContext,
                Looper.getMainLooper()).getAllDigestsForReport(
                new WatchlistReportDbHelper.AggregatedResult(new HashSet<String>(), null,
                        new HashMap<String, String>()));
        assertEquals(2, result.size());
        assertEquals(APK_A_CONTENT_HASH, result.get(0));
        assertEquals(APK_B_CONTENT_HASH, result.get(1));
    }

    @Test
    public void testWatchlistLoggingHandler_getAllSubDomains() throws Exception {
        String[] subDomains = WatchlistLoggingHandler.getAllSubDomains("abc.def.gh.i.jkl.mm");
        assertTrue(Arrays.equals(subDomains, new String[]{"abc.def.gh.i.jkl.mm",
                "def.gh.i.jkl.mm", "gh.i.jkl.mm", "i.jkl.mm", "jkl.mm", "mm"}));
        subDomains = WatchlistLoggingHandler.getAllSubDomains(null);
        assertNull(subDomains);
        subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm");
        assertTrue(Arrays.equals(subDomains, new String[]{"jkl.mm", "mm"}));
        subDomains = WatchlistLoggingHandler.getAllSubDomains("abc");
        assertTrue(Arrays.equals(subDomains, new String[]{"abc"}));
        subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm.");
        assertTrue(Arrays.equals(subDomains, new String[]{"jkl.mm.", "mm."}));
    }
}
