/*
 * 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.car.content.pm;

import static android.view.Display.DEFAULT_DISPLAY;

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

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.car.CarVersion;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.platform.test.annotations.DisabledOnRavenwood;

import com.android.car.internal.ICarBase;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.concurrent.Executor;

@RunWith(MockitoJUnitRunner.class)
public final class CarPackageManagerUnitTest {

    @Mock
    private ICarBase mCar;

    @Mock
    private ICarPackageManager mService;

    @Mock
    private Executor mExecutor;

    @Mock
    private CarPackageManager.BlockingUiCommandListener mBlockingUiCommandListener;

    private CarPackageManager mMgr;

    @Before
    public void setFixtures() {
        mMgr = new CarPackageManager(mCar, mService);
    }

    @Test
    public void testGetTargetCarVersion_self_ok() throws Exception {
        mockPackageName("dr.evil");
        CarVersion apiVersion = CarVersion.forMajorAndMinorVersions(66, 6);
        when(mService.getSelfTargetCarVersion("dr.evil")).thenReturn(apiVersion);

        assertThat(mMgr.getTargetCarVersion()).isSameInstanceAs(apiVersion);
    }

    @Test
    public void testGetTargetCarVersion_self_remoteException() throws Exception {
        mockPackageName("the.meaning.of.life");
        RemoteException cause = new RemoteException("D'OH!");
        when(mService.getSelfTargetCarVersion("the.meaning.of.life")).thenThrow(cause);

        RuntimeException e = assertThrows(RuntimeException.class, () -> mMgr.getTargetCarVersion());

        assertThat(e.getCause()).isSameInstanceAs(cause);
    }

    @Test
    public void testGetTargetCarVersion_ok() throws Exception {
        CarVersion apiVersion = CarVersion.forMajorAndMinorVersions(66, 6);
        when(mService.getTargetCarVersion("dr.evil")).thenReturn(apiVersion);

        assertThat(mMgr.getTargetCarVersion("dr.evil")).isSameInstanceAs(apiVersion);
    }

    @Test
    public void testGetTargetCarVersion_runtimeException() throws Exception {
        RemoteException cause = new RemoteException("D'OH!");
        when(mService.getTargetCarVersion("the.meaning.of.life")).thenThrow(cause);

        RuntimeException e = assertThrows(RuntimeException.class,
                () -> mMgr.getTargetCarVersion("the.meaning.of.life"));

        assertThat(e.getCause()).isSameInstanceAs(cause);
    }

    @Test
    public void testGetTargetCarVersion_serviceException_unexpectedErrorCode() throws Exception {
        ServiceSpecificException cause = new ServiceSpecificException(666, "D'OH!");
        when(mService.getTargetCarVersion("the.meaning.of.life")).thenThrow(cause);

        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> mMgr.getTargetCarVersion("the.meaning.of.life"));

        assertThat(e.getCause()).isSameInstanceAs(cause);
    }

    @Test
    @DisabledOnRavenwood(blockedBy = NameNotFoundException.class)
    public void testGetTargetCarVersion_serviceException_notFound() throws Exception {
        when(mService.getTargetCarVersion("the.meaning.of.life"))
                .thenThrow(new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
                        "D'OH!"));

        NameNotFoundException e = assertThrows(NameNotFoundException.class,
                () -> mMgr.getTargetCarVersion("the.meaning.of.life"));

        assertThat(e.getMessage()).contains("the.meaning.of.life");
        assertThat(e.getMessage()).doesNotContain("D'OH!");
    }

    @Test
    public void registerBlockingUiCommandListener() throws Exception {
        mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
                mBlockingUiCommandListener);

        verify(mService).registerBlockingUiCommandListener(any(), anyInt());
    }

    @Test
    public void registerBlockingUiCommandListener_sameListenerNotRegisterForAnotherDisplay()
            throws Exception {
        int tempDisplayId = 1;

        mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
                mBlockingUiCommandListener);

        assertThrows(IllegalStateException.class,
                () -> mMgr.registerBlockingUiCommandListener(tempDisplayId, mExecutor,
                        mBlockingUiCommandListener));
    }

    @Test
    public void unregisterBlockingUiCommandListener() throws Exception {
        mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
                mBlockingUiCommandListener);

        mMgr.unregisterBlockingUiCommandListener(mBlockingUiCommandListener);

        verify(mService).unregisterBlockingUiCommandListener(any());
    }

    private void mockPackageName(String name) {
        Context context = mock(Context.class);
        when(context.getPackageName()).thenReturn(name);
        when(mCar.getContext()).thenReturn(context);
    }
}
