/*
 * 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.car.settings.security;

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

import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.os.RemoteException;

import com.android.car.settings.setupservice.InitialLockSetupService;
import com.android.car.settings.testutils.ShadowLockPatternUtils;
import com.android.car.setupwizardlib.IInitialLockSetupService;
import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
import com.android.car.setupwizardlib.InitialLockSetupHelper;
import com.android.car.setupwizardlib.LockConfig;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContextWrapper;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Tests that the {@link InitialLockSetupService} properly handles connections and lock requests.
 */
@Config(shadows = ShadowLockPatternUtils.class)
@RunWith(RobolectricTestRunner.class)
public class InitialLockSetupServiceTest {

    private static final String LOCK_PERMISSION = "com.android.car.settings.SET_INITIAL_LOCK";

    private InitialLockSetupService mInitialLockSetupService;
    private Context mContext;

    @Before
    public void setupService() {
        ShadowLockPatternUtils.reset();
        mInitialLockSetupService = Robolectric.buildService(InitialLockSetupService.class)
                .create()
                .get();
        mContext = RuntimeEnvironment.application;
        ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
        shadowContextWrapper.grantPermissions(LOCK_PERMISSION);
    }

    @Test
    public void testBindReturnsNull_ifLockSet() {
        ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
        assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
    }

    @Test
    public void testBindReturnsInstanceOfServiceInterface_ifLockNotSet() throws RemoteException {
        assertThat(mInitialLockSetupService.onBind(
                new Intent()) instanceof IInitialLockSetupService.Stub).isTrue();
    }

    @Test
    public void testGetLockConfig_returnsCorrectConfig() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        LockConfig pinConfig = service.getLockConfig(LockTypes.PIN);
        assertThat(pinConfig.enabled).isTrue();
        assertThat(pinConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
        LockConfig patternConfig = service.getLockConfig(LockTypes.PATTERN);
        assertThat(patternConfig.enabled).isTrue();
        assertThat(patternConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PATTERN_SIZE);
        LockConfig passwordConfig = service.getLockConfig(LockTypes.PASSWORD);
        assertThat(passwordConfig.enabled).isTrue();
        assertThat(passwordConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
    }

    @Test
    public void testCheckValidLock_tooShort() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        int result = service.checkValidLock(LockTypes.PASSWORD, "hi".getBytes());
        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
    }

    @Test
    public void testCheckValidLock_longEnough() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        int result = service.checkValidLock(LockTypes.PASSWORD, "password".getBytes());
        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
                .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
    }

    @Test
    public void testCheckValidLockPin_withLetters() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        int result = service.checkValidLock(LockTypes.PIN, "12a3".getBytes());
        assertThat(result & ValidateLockFlags.INVALID_BAD_SYMBOLS)
                .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
    }

    @Test
    public void testCheckValidLockPattern_tooShort() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE - 1];
        for (int i = 0; i < pattern.length; i++) {
            pattern[i] = (byte) i;
        }
        int result = service.checkValidLock(LockTypes.PATTERN, pattern);
        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
    }

    @Test
    public void testCheckValidLockPattern_longEnough() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE + 1];
        for (int i = 0; i < pattern.length; i++) {
            pattern[i] = (byte) i;
        }
        int result = service.checkValidLock(LockTypes.PATTERN, pattern);
        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
                .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
    }

    @Test
    public void testSetLockPassword_doesNotWorkWithExistingPassword() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
        int result = service.setLock(LockTypes.PASSWORD, "password".getBytes());
        assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_EXISTS);
    }

    @Test
    public void testSetLockPassword_doesNotWorkWithInvalidPassword() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        int result = service.setLock(LockTypes.PASSWORD, "hi".getBytes());
        assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_INVALID);
    }

    @Test
    public void testSetLockPassword_setsDevicePassword() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        byte[] password = "password".getBytes();
        // Need copy since password is cleared.
        byte[] expectedPassword = Arrays.copyOf(password, password.length);
        int result = service.setLock(LockTypes.PASSWORD, password);
        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
        assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
                expectedPassword)).isTrue();
    }

    @Test
    public void testSetLockPin_setsDevicePin() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        byte[] password = "1580".getBytes();
        byte[] expectedPassword = Arrays.copyOf(password, password.length);
        int result = service.setLock(LockTypes.PIN, password);
        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
        assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
                expectedPassword)).isTrue();
    }

    @Test
    public void testSetLockPattern_setsDevicePattern() throws RemoteException {
        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
                mInitialLockSetupService.onBind(new Intent()));
        List<LockPatternView.Cell> pattern = new ArrayList<>();
        pattern.add(LockPatternView.Cell.of(0, 0));
        pattern.add(LockPatternView.Cell.of(1, 0));
        pattern.add(LockPatternView.Cell.of(2, 0));
        pattern.add(LockPatternView.Cell.of(0, 1));
        byte[] patternBytes = new byte[pattern.size()];
        for (int i = 0; i < patternBytes.length; i++) {
            LockPatternView.Cell cell = pattern.get(i);
            patternBytes[i] = InitialLockSetupHelper.getByteFromPatternCell(cell.getRow(),
                    cell.getColumn());
        }
        int result = service.setLock(LockTypes.PATTERN, patternBytes);
        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
        byte[] savedPattern = ShadowLockPatternUtils.getSavedPattern();
        assertThat(savedPattern).isEqualTo(LockPatternUtils.patternToByteArray(pattern));
    }

    @Test
    public void testBindFails_ifNoPermissionGranted() {
        ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
        shadowContextWrapper.denyPermissions(LOCK_PERMISSION);
        assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
    }

}
