/*
 * Copyright (C) 2015 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.messaging.datamodel;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;

import androidx.test.filters.SmallTest;

import com.android.messaging.BugleTestCase;
import com.android.messaging.FakeContentProvider;
import com.android.messaging.FakeContext;
import com.android.messaging.FakeFactory;
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.ContactUtil;

import org.junit.Assert;
import org.mockito.Mock;

/**
 * Utility class for testing ParticipantRefresh class for different scenarios.
 */
@SmallTest
public class ParticipantRefreshTest extends BugleTestCase {
    private FakeContext mContext;
    FakeFactory mFakeFactory;
    @Mock protected UIIntents mMockUIIntents;
    protected FakeDataModel mFakeDataModel;

    @Override
    public void setUp() throws Exception {
        super.setUp();

        mContext = new FakeContext(getTestContext());

        final ContentProvider provider = new MessagingContentProvider();
        provider.attachInfo(mContext, null);
        mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider);

        mFakeDataModel = new FakeDataModel(mContext);
        mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext)
                .withDataModel(mFakeDataModel)
                .withUIIntents(mMockUIIntents);
    }

    /**
     * Add some phonelookup result into take PhoneLookup content provider. This will be
     * used for doing phone lookup during participant refresh.
     */
    private void addPhoneLookup(final String phone, final Object[][] lookupResult) {
        final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri();
        final FakeContentProvider phoneLookup = new FakeContentProvider(mContext,
                uri, false);
        phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION,
                lookupResult);
        mFakeFactory.withProvider(uri, phoneLookup);
    }

    /**
     * Add some participant to test database.
     */
    private void addParticipant(final String normalizedDestination, final long contactId,
            final String name, final String photoUrl) {
        final DatabaseWrapper db = DataModel.get().getDatabase();
        final ContentValues values = new ContentValues();

        values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination);
        values.put(ParticipantColumns.CONTACT_ID, contactId);
        values.put(ParticipantColumns.FULL_NAME, name);
        values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl);

        db.beginTransaction();
        try {
            db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

    /**
     * Verify that participant in the database has expected contacdtId, name and photoUrl fields.
     */
    private void verifyParticipant(final String normalizedDestination, final long contactId,
            final String name, final String photoUrl) {
        final DatabaseWrapper db = DataModel.get().getDatabase();
        db.beginTransaction();
        try {
            final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?";
            final String[] selectionArgs = new String[] { normalizedDestination };

            final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
                    ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null);

            if (cursor == null || cursor.getCount() != 1) {
                Assert.fail("Should have participants for:" + normalizedDestination);
                return;
            }

            cursor.moveToFirst();
            final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID);
            final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
            final String currentPhotoUrl =
                    cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
            if (currentContactId != contactId) {
                Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
                        " expected=" + contactId + " actual=" + currentContactId);
                return;
            }

            if (!TextUtils.equals(currentName, name)) {
                Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination +
                        " expected=" + name + " actual=" + currentName);
                return;
            }

            if (!TextUtils.equals(currentPhotoUrl, photoUrl)) {
                Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
                        " expected=" + photoUrl + " actual=" + currentPhotoUrl);
                return;
            }

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

    /**
     * Verify that incremental refresh will resolve previously not resolved participants.
     */
    public void testIncrementalRefreshNotResolvedSingleMatch() {
        addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
                null, null);
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that incremental refresh will resolve previously not resolved participants.
     */
    public void testIncrementalRefreshNotResolvedMultiMatch() {
        addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
                null, null);
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
                { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that incremental refresh will not touch already-resolved participants.
     */
    public void testIncrementalRefreshResolvedSingleMatch() {
        addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that full refresh will correct already-resolved participants if needed
     */
    public void testFullRefreshResolvedSingleMatch() {
        addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that incremental refresh will not touch participant that is marked as not found.
     */
    public void testIncrementalRefreshNotFound() {
        addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
                null, null);
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that full refresh will resolve participant that is marked as not found.
     */
    public void testFullRefreshNotFound() {
        addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
                null, null);
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that refresh take consideration of current contact_id when having multiple matches.
     */
    public void testFullRefreshResolvedMultiMatch1() {
        addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
                { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that refresh take consideration of current contact_id when having multiple matches.
     */
    public void testFullRefreshResolvedMultiMatch2() {
        addParticipant("650-123-1233", 2, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
                { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that refresh take first contact in case current contact_id no longer matches.
     */
    public void testFullRefreshResolvedMultiMatch3() {
        addParticipant("650-123-1233", 3, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {
                { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
                { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
        });

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
    }

    /**
     * Verify that refresh take first contact in case current contact_id no longer matches.
     */
    public void testFullRefreshResolvedBeforeButNotFoundNow() {
        addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
        addPhoneLookup("650-123-1233", new Object[][] {});

        ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
        verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
                null, null);
    }
}
