/*
 * Copyright (C) 2022 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 android.health.connect;

import static android.health.connect.HealthPermissions.READ_EXERCISE_ROUTE;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_CONDITIONS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_LABORATORY_RESULTS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_MEDICATIONS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_PERSONAL_DETAILS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_PRACTITIONER_DETAILS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_PREGNANCY;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_PROCEDURES;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_SOCIAL_HISTORY;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_VACCINES;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_VISITS;
import static android.health.connect.HealthPermissions.READ_MEDICAL_DATA_VITAL_SIGNS;
import static android.health.connect.HealthPermissions.WRITE_MEDICAL_DATA;
import static android.health.connect.HealthPermissions.isValidHealthPermission;

import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD;
import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE;

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

import static org.mockito.Mockito.reset;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.android.healthfitness.flags.Flags;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RunWith(AndroidJUnit4.class)
public class HealthPermissionsTest {
    private static final String FAIL_MESSAGE =
            "Add new health permission to ALL_EXPECTED_HEALTH_PERMISSIONS and "
                    + " android.healthconnect.cts.HealthPermissionsPresenceTest.HEALTH_PERMISSIONS "
                    + "sets.";

    private static final String PHR_FAIL_MESSAGE =
            "Add new medical permissions to ALL_EXPECTED_HEALTH_PERMISSIONS and "
                    + "apk/HealthPermissionsManifest.xml.";

    private static final String MEDICAL_PERMISSION_IDENTIFIER = "MEDICAL_DATA";

    // Add new health permission to ALL_EXPECTED_HEALTH_PERMISSIONS and
    // {@link android.healthconnect.cts.HealthPermissionsPresenceTest.HEALTH_PERMISSIONS}
    // sets.
    private static final Set<String> ALL_EXPECTED_HEALTH_PERMISSIONS =
            Stream.concat(
                            Stream.of(
                                    HealthPermissions.READ_HEALTH_DATA_HISTORY,
                                    HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND,
                                    HealthPermissions.READ_ACTIVE_CALORIES_BURNED,
                                    HealthPermissions.READ_DISTANCE,
                                    HealthPermissions.READ_ELEVATION_GAINED,
                                    HealthPermissions.READ_EXERCISE,
                                    HealthPermissions.READ_EXERCISE_ROUTES,
                                    HealthPermissions.READ_PLANNED_EXERCISE,
                                    HealthPermissions.READ_FLOORS_CLIMBED,
                                    HealthPermissions.READ_STEPS,
                                    HealthPermissions.READ_TOTAL_CALORIES_BURNED,
                                    HealthPermissions.READ_VO2_MAX,
                                    HealthPermissions.READ_WHEELCHAIR_PUSHES,
                                    HealthPermissions.READ_POWER,
                                    HealthPermissions.READ_SPEED,
                                    HealthPermissions.READ_BASAL_METABOLIC_RATE,
                                    HealthPermissions.READ_BODY_FAT,
                                    HealthPermissions.READ_BODY_WATER_MASS,
                                    HealthPermissions.READ_BONE_MASS,
                                    HealthPermissions.READ_HEIGHT,
                                    HealthPermissions.READ_LEAN_BODY_MASS,
                                    HealthPermissions.READ_WEIGHT,
                                    HealthPermissions.READ_CERVICAL_MUCUS,
                                    HealthPermissions.READ_INTERMENSTRUAL_BLEEDING,
                                    HealthPermissions.READ_MENSTRUATION,
                                    HealthPermissions.READ_OVULATION_TEST,
                                    HealthPermissions.READ_SEXUAL_ACTIVITY,
                                    HealthPermissions.READ_HYDRATION,
                                    HealthPermissions.READ_NUTRITION,
                                    HealthPermissions.READ_SLEEP,
                                    HealthPermissions.READ_BASAL_BODY_TEMPERATURE,
                                    HealthPermissions.READ_BLOOD_GLUCOSE,
                                    HealthPermissions.READ_BLOOD_PRESSURE,
                                    HealthPermissions.READ_BODY_TEMPERATURE,
                                    HealthPermissions.READ_HEART_RATE,
                                    HealthPermissions.READ_HEART_RATE_VARIABILITY,
                                    HealthPermissions.READ_OXYGEN_SATURATION,
                                    HealthPermissions.READ_RESPIRATORY_RATE,
                                    HealthPermissions.READ_RESTING_HEART_RATE,
                                    HealthPermissions.READ_SKIN_TEMPERATURE,
                                    HealthPermissions.READ_MINDFULNESS,
                                    HealthPermissions.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES,
                                    HealthPermissions.READ_MEDICAL_DATA_CONDITIONS,
                                    HealthPermissions.READ_MEDICAL_DATA_LABORATORY_RESULTS,
                                    HealthPermissions.READ_MEDICAL_DATA_MEDICATIONS,
                                    HealthPermissions.READ_MEDICAL_DATA_PERSONAL_DETAILS,
                                    HealthPermissions.READ_MEDICAL_DATA_PRACTITIONER_DETAILS,
                                    HealthPermissions.READ_MEDICAL_DATA_PREGNANCY,
                                    HealthPermissions.READ_MEDICAL_DATA_PROCEDURES,
                                    HealthPermissions.READ_MEDICAL_DATA_SOCIAL_HISTORY,
                                    HealthPermissions.READ_MEDICAL_DATA_VACCINES,
                                    HealthPermissions.READ_MEDICAL_DATA_VISITS,
                                    HealthPermissions.READ_MEDICAL_DATA_VITAL_SIGNS,
                                    HealthPermissions.WRITE_ACTIVE_CALORIES_BURNED,
                                    HealthPermissions.WRITE_DISTANCE,
                                    HealthPermissions.WRITE_ELEVATION_GAINED,
                                    HealthPermissions.WRITE_EXERCISE,
                                    HealthPermissions.WRITE_EXERCISE_ROUTE,
                                    HealthPermissions.WRITE_PLANNED_EXERCISE,
                                    HealthPermissions.WRITE_FLOORS_CLIMBED,
                                    HealthPermissions.WRITE_STEPS,
                                    HealthPermissions.WRITE_TOTAL_CALORIES_BURNED,
                                    HealthPermissions.WRITE_VO2_MAX,
                                    HealthPermissions.WRITE_WHEELCHAIR_PUSHES,
                                    HealthPermissions.WRITE_POWER,
                                    HealthPermissions.WRITE_SPEED,
                                    HealthPermissions.WRITE_BASAL_METABOLIC_RATE,
                                    HealthPermissions.WRITE_BODY_FAT,
                                    HealthPermissions.WRITE_BODY_WATER_MASS,
                                    HealthPermissions.WRITE_BONE_MASS,
                                    HealthPermissions.WRITE_HEIGHT,
                                    HealthPermissions.WRITE_LEAN_BODY_MASS,
                                    HealthPermissions.WRITE_WEIGHT,
                                    HealthPermissions.WRITE_CERVICAL_MUCUS,
                                    HealthPermissions.WRITE_INTERMENSTRUAL_BLEEDING,
                                    HealthPermissions.WRITE_MENSTRUATION,
                                    HealthPermissions.WRITE_OVULATION_TEST,
                                    HealthPermissions.WRITE_SEXUAL_ACTIVITY,
                                    HealthPermissions.WRITE_HYDRATION,
                                    HealthPermissions.WRITE_NUTRITION,
                                    HealthPermissions.WRITE_SLEEP,
                                    HealthPermissions.WRITE_BASAL_BODY_TEMPERATURE,
                                    HealthPermissions.WRITE_BLOOD_GLUCOSE,
                                    HealthPermissions.WRITE_BLOOD_PRESSURE,
                                    HealthPermissions.WRITE_BODY_TEMPERATURE,
                                    HealthPermissions.WRITE_HEART_RATE,
                                    HealthPermissions.WRITE_HEART_RATE_VARIABILITY,
                                    HealthPermissions.WRITE_OXYGEN_SATURATION,
                                    HealthPermissions.WRITE_RESPIRATORY_RATE,
                                    HealthPermissions.WRITE_RESTING_HEART_RATE,
                                    HealthPermissions.WRITE_SKIN_TEMPERATURE,
                                    HealthPermissions.WRITE_MINDFULNESS,
                                    HealthPermissions.WRITE_MEDICAL_DATA),
                            Flags.activityIntensity()
                                    ? Stream.of(
                                            HealthPermissions.READ_ACTIVITY_INTENSITY,
                                            HealthPermissions.WRITE_ACTIVITY_INTENSITY)
                                    : Stream.of())
                    .collect(Collectors.toSet());
    private PackageManager mPackageManager;
    private Context mContext;
    @Mock private PackageInfo mPackageInfo1;

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = InstrumentationRegistry.getTargetContext();
        mPackageManager = mContext.getPackageManager();
    }

    @After
    public void tearDown() {
        reset(mPackageInfo1);
    }

    @Test
    public void testHealthGroupPermissions_noUnexpectedPermissionsDefined() throws Exception {
        PermissionInfo[] permissionInfos = getHealthPermissionInfos();
        for (PermissionInfo permissionInfo : permissionInfos) {
            if (isValidHealthPermission(permissionInfo)) {
                assertWithMessage(FAIL_MESSAGE)
                        .that(ALL_EXPECTED_HEALTH_PERMISSIONS)
                        .contains(permissionInfo.name);
            }
        }
    }

    @Test
    public void testHealthConnectManager_noUnexpectedPermissionsReturned() throws Exception {
        assertWithMessage(FAIL_MESSAGE)
                .that(HealthConnectManager.getHealthPermissions(mContext))
                .isEqualTo(ALL_EXPECTED_HEALTH_PERMISSIONS);
    }

    @Test
    public void testReadExerciseRoutePerm_hasSignatureProtection() throws Exception {
        assertThat(
                        mPackageManager
                                .getPermissionInfo(READ_EXERCISE_ROUTE, /* flags= */ 0)
                                .getProtection())
                .isEqualTo(PermissionInfo.PROTECTION_SIGNATURE);
    }

    @Test
    public void testReadExerciseRoutePerm_controllerUiHoldsIt() throws Exception {
        String healthControllerPackageName = getControllerUiPackageName();
        assertThat(
                        mContext.getPackageManager()
                                .checkPermission(READ_EXERCISE_ROUTE, healthControllerPackageName))
                .isEqualTo(PackageManager.PERMISSION_GRANTED);
    }

    @Test
    public void testGetDataCategoriesWithWritePermissionsForPackage_returnsCorrectSet() {
        mPackageInfo1.requestedPermissions =
                new String[] {
                    HealthPermissions.WRITE_STEPS,
                    HealthPermissions.WRITE_SLEEP,
                    HealthPermissions.READ_HEART_RATE,
                    HealthPermissions.WRITE_OVULATION_TEST
                };
        mPackageInfo1.requestedPermissionsFlags =
                new int[] {
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    0,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED
                };

        Set<Integer> expectedResult =
                Set.of(HealthDataCategory.ACTIVITY, HealthDataCategory.CYCLE_TRACKING);
        Set<Integer> actualResult =
                HealthPermissions.getDataCategoriesWithWritePermissionsForPackage(
                        mPackageInfo1, mContext);
        assertThat(actualResult).isEqualTo(expectedResult);
    }

    @Test
    public void
            testPackageHasWriteHealthPermissionsForCategory_ifNoWritePermissions_returnsFalse() {
        mPackageInfo1.requestedPermissions =
                new String[] {
                    HealthPermissions.WRITE_EXERCISE,
                    HealthPermissions.WRITE_SLEEP,
                    HealthPermissions.READ_HEART_RATE,
                    HealthPermissions.WRITE_OVULATION_TEST
                };
        mPackageInfo1.requestedPermissionsFlags =
                new int[] {
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    0,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED
                };

        assertThat(
                        HealthPermissions.getPackageHasWriteHealthPermissionsForCategory(
                                mPackageInfo1, HealthDataCategory.SLEEP, mContext))
                .isFalse();
    }

    @Test
    public void testPackageHasWriteHealthPermissionsForCategory_ifWritePermissions_returnsTrue() {
        mPackageInfo1.requestedPermissions =
                new String[] {
                    HealthPermissions.WRITE_STEPS,
                    HealthPermissions.WRITE_SLEEP,
                    HealthPermissions.READ_HEART_RATE,
                    HealthPermissions.WRITE_OVULATION_TEST
                };
        mPackageInfo1.requestedPermissionsFlags =
                new int[] {
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    0,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED,
                    PackageInfo.REQUESTED_PERMISSION_GRANTED
                };

        assertThat(
                        HealthPermissions.getPackageHasWriteHealthPermissionsForCategory(
                                mPackageInfo1, HealthDataCategory.ACTIVITY, mContext))
                .isTrue();
    }

    @Test
    @EnableFlags({FLAG_PERSONAL_HEALTH_RECORD, FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
    public void testGetMedicalPermissions_returnsValidPermissions() {
        Set<String> permissions = HealthPermissions.getAllMedicalPermissions();
        assertThat(permissions)
                .containsAtLeast(
                        WRITE_MEDICAL_DATA,
                        READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES,
                        READ_MEDICAL_DATA_CONDITIONS,
                        READ_MEDICAL_DATA_LABORATORY_RESULTS,
                        READ_MEDICAL_DATA_MEDICATIONS,
                        READ_MEDICAL_DATA_PERSONAL_DETAILS,
                        READ_MEDICAL_DATA_PRACTITIONER_DETAILS,
                        READ_MEDICAL_DATA_PREGNANCY,
                        READ_MEDICAL_DATA_PROCEDURES,
                        READ_MEDICAL_DATA_SOCIAL_HISTORY,
                        READ_MEDICAL_DATA_VACCINES,
                        READ_MEDICAL_DATA_VISITS,
                        READ_MEDICAL_DATA_VITAL_SIGNS);
    }

    @Test
    @EnableFlags({FLAG_PERSONAL_HEALTH_RECORD, FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
    public void testGetMedicalPermissions_returnsAllMedicalPermissions() throws Exception {
        Set<String> permissions = HealthPermissions.getAllMedicalPermissions();

        PermissionInfo[] permissionInfos = getHealthPermissionInfos();
        for (PermissionInfo permissionInfo : permissionInfos) {
            if (isValidHealthPermission(permissionInfo)
                    && permissionInfo.name.contains(MEDICAL_PERMISSION_IDENTIFIER)) {
                assertWithMessage(PHR_FAIL_MESSAGE).that(permissions).contains(permissionInfo.name);
            }
        }
    }

    private PermissionInfo[] getHealthPermissionInfos() throws Exception {
        String healthControllerPackageName = getControllerUiPackageName();

        PackageInfo packageInfo =
                mPackageManager.getPackageInfo(
                        healthControllerPackageName,
                        PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS));
        return packageInfo.permissions;
    }

    private String getControllerUiPackageName() throws Exception {
        return mPackageManager.getPermissionInfo(HealthPermissions.READ_STEPS, /* flags= */ 0)
                .packageName;
    }
}
