package protect.budgetwatch;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
public class BudgetViewActivityTest
{
    @Before
    public void setUp()
    {
        // Output logs emitted during tests so they may be accessed
        ShadowLog.stream = System.out;
    }

    enum ViewMode
    {
        ADD_CARD,
        VIEW_CARD,
        UPDATE_CARD,
        ;
    }

    @Test
    public void clickBackFinishes()
    {
        final Activity activity = Robolectric.setupActivity(BudgetViewActivity.class);

        shadowOf(activity).clickMenuItem(android.R.id.home);
        assertTrue(shadowOf(activity).isFinishing());
    }

    private ActivityController setupActivity(final String budget, int value,
                                             boolean launchAsView, boolean launchAsUpdate)
    {
        Intent intent = new Intent();
        final Bundle bundle = new Bundle();

        if(budget != null)
        {
            bundle.putString("id", budget);
        }

        if(launchAsView)
        {
            bundle.putBoolean("view", true);
        }

        if(launchAsUpdate)
        {
            bundle.putBoolean("update", true);
        }

        intent.putExtras(bundle);

        ActivityController activityController = Robolectric.buildActivity(BudgetViewActivity.class, intent).create();

        Activity activity = (Activity)activityController.get();
        DBHelper db = new DBHelper(activity);

        if(budget != null)
        {
            boolean result = db.insertBudget(budget, value);
            assertTrue(result);
        }
        db.close();

        activityController.start();
        activityController.visible();
        activityController.resume();

        return activityController;
    }

    private void checkAllFields(final Activity activity, ViewMode mode,
                                final String budget, final String value)
    {
        int viewVisibility = (mode == ViewMode.VIEW_CARD) ? View.VISIBLE : View.GONE;
        int editVisibility = (mode != ViewMode.VIEW_CARD) ? View.VISIBLE : View.GONE;

        checkFieldProperties(activity, R.id.budgetNameEdit, editVisibility, mode == ViewMode.VIEW_CARD ? "" : budget);
        checkFieldProperties(activity, R.id.budgetNameView, viewVisibility, mode == ViewMode.VIEW_CARD ? budget : "");
        checkFieldProperties(activity, R.id.valueEdit, editVisibility, mode == ViewMode.VIEW_CARD ? "" : value);
        checkFieldProperties(activity, R.id.valueView, viewVisibility, mode == ViewMode.VIEW_CARD ? value : "");
    }

    private void checkFieldProperties(final Activity activity, final int id, final int visibility,
                                      final String contents)
    {
        final View view = activity.findViewById(id);
        assertNotNull(view);
        assertEquals(visibility, view.getVisibility());
        if(contents != null)
        {
            TextView textView = (TextView)view;
            assertEquals(contents, textView.getText().toString());
        }
    }

    @Test
    public void startAsAddCheckFieldsAvailable()
    {
        ActivityController activityController = setupActivity(null, 0, false, false);

        Activity activity = (Activity)activityController.get();

        checkAllFields(activity, ViewMode.ADD_CARD, "", "");
    }

    @Test
    public void startAsAddCannotCreateBudget()
    {
        ActivityController activityController = setupActivity(null, 0, false, false);

        Activity activity = (Activity)activityController.get();
        DBHelper db = new DBHelper(activity);
        assertEquals(0, db.getBudgetCount());

        for(String[] test : Arrays.asList(
                new String[]{"", ""},
                new String[]{"", "100"},
                new String[]{"budget", ""},
                new String[]{"budget", "NotANumber"}
        ))
        {
            String budget = test[0];
            String value = test[1];

            final EditText budgetNameView = (EditText) activity.findViewById(R.id.budgetNameEdit);
            budgetNameView.setText(budget);

            final EditText valueField = (EditText) activity.findViewById(R.id.valueEdit);
            valueField.setText(value);

            // Perform the actual test, no transaction should be created
            shadowOf(activity).clickMenuItem(R.id.action_save);
            assertEquals(0, db.getBudgetCount());
        }

        db.close();
    }

    /**
     * Save a budget and check that the database contains the
     * expected value
     */
    private void saveBudgetWithArguments(final Activity activity,
                                          final String budgetName, final int value,
                                          boolean creatingNewBudget)
    {
        DBHelper db = new DBHelper(activity);
        if(creatingNewBudget)
        {
            assertEquals(0, db.getBudgetCount());
        }
        else
        {
            assertEquals(1, db.getBudgetCount());
        }
        db.close();

        final EditText budgetField = (EditText) activity.findViewById(R.id.budgetNameEdit);
        final EditText valueField = (EditText) activity.findViewById(R.id.valueEdit);

        budgetField.setText(budgetName);
        valueField.setText(Integer.toString(value));

        assertEquals(false, activity.isFinishing());
        shadowOf(activity).clickMenuItem(R.id.action_save);
        assertEquals(true, activity.isFinishing());

        assertEquals(1, db.getBudgetCount());

        List<Budget> budgets = db.getBudgets(0, 0);
        assertEquals(1, budgets.size());
        Budget budget = budgets.get(0);

        assertEquals(budgetName, budget.name);
        assertEquals(value, budget.max);
        assertEquals(0, budget.current);
    }

    @Test
    public void startAsAddCreateBudget()
    {
        ActivityController activityController = setupActivity(null, 0, false, false);

        Activity activity = (Activity)activityController.get();

        saveBudgetWithArguments(activity, "budgetName", 100, true);
    }

    @Test
    public void startAsEditCheckDisplay() throws IOException
    {
        ActivityController activityController = setupActivity("budgetName", 100, false, true);

        Activity activity = (Activity)activityController.get();

        checkAllFields(activity, ViewMode.UPDATE_CARD, "budgetName", "100");
    }

    @Test
    public void startAsEditUpdateValue() throws IOException
    {
        ActivityController activityController = setupActivity("budgetName", 100, false, true);
        Activity activity = (Activity)activityController.get();

        checkAllFields(activity, ViewMode.UPDATE_CARD, "budgetName", "100");

        saveBudgetWithArguments(activity, "budgetName", 1234, false);
    }

    @Test
    public void startAsViewCheckDisplay() throws IOException
    {
        ActivityController activityController = setupActivity("budgetName", 100, true, false);

        Activity activity = (Activity)activityController.get();

        checkAllFields(activity, ViewMode.VIEW_CARD, "budgetName", "100");
    }

    @Test
    public void startAsAddCheckActionBar() throws Exception
    {
        ActivityController activityController = setupActivity(null, 0, false, false);
        Activity activity = (Activity)activityController.get();

        final Menu menu = shadowOf(activity).getOptionsMenu();
        assertTrue(menu != null);

        assertEquals(menu.size(), 1);

        MenuItem item = menu.findItem(R.id.action_save);
        assertNotNull(item);
        assertEquals("Save", item.getTitle().toString());
    }

    @Test
    public void startAsUpdateCheckActionBar() throws Exception
    {
        ActivityController activityController = setupActivity("budgetName", 100, false, true);
        Activity activity = (Activity)activityController.get();

        final Menu menu = shadowOf(activity).getOptionsMenu();
        assertTrue(menu != null);

        assertEquals(menu.size(), 2);

        MenuItem item = menu.findItem(R.id.action_delete);
        assertNotNull(item);
        assertEquals("Delete", item.getTitle().toString());

        item = menu.findItem(R.id.action_save);
        assertNotNull(item);
        assertEquals("Save", item.getTitle().toString());
    }

    @Test
    public void clickDeleteRemovesBudget()
    {
        ActivityController activityController = setupActivity("budgetName", 100, false, true);
        Activity activity = (Activity)activityController.get();
        DBHelper db = new DBHelper(activity);

        assertEquals(1, db.getBudgetCount());
        shadowOf(activity).clickMenuItem(R.id.action_delete);

        // TODO: Finish this test once robolectric has shadows of the android.support AlertDialog class.
        // https://github.com/robolectric/robolectric/issues/1944

        /*
        // A dialog should be displayed now
        AlertDialog alert = ShadowAlertDialog.getLatestAlertDialog();
        assertNotNull(alert);
        ShadowAlertDialog sAlert = shadowOf(alert);
        assertEquals(sAlert.getTitle().toString(), activity.getString(R.string.deleteTransactionTitle));
        assertEquals(sAlert.getMessage().toString(), activity.getString(R.string.deleteTransactionConfirmation));

        alert.clickOnText(R.string.confirm);
        assertEquals(0, db.getBudgetCount());
        */

        db.close();
    }

    @Test
    public void startAsViewCheckActionBar() throws Exception
    {
        ActivityController activityController = setupActivity("budget", 100, true, false);
        Activity activity = (Activity)activityController.get();

        final Menu menu = shadowOf(activity).getOptionsMenu();
        assertTrue(menu != null);

        assertEquals(menu.size(), 1);

        MenuItem item = menu.findItem(R.id.action_edit);
        assertNotNull(item);
        assertEquals("Edit", item.getTitle().toString());
    }

    @Test
    public void clickEditLaunchesTransactionViewActivity()
    {
        ActivityController activityController = setupActivity("budgetName", 100, true, false);
        Activity activity = (Activity)activityController.get();

        shadowOf(activity).clickMenuItem(R.id.action_edit);

        Intent intent = shadowOf(activity).peekNextStartedActivityForResult().intent;

        assertEquals(new ComponentName(activity, BudgetViewActivity.class), intent.getComponent());
        Bundle bundle = intent.getExtras();
        assertNotNull(bundle);
        assertEquals("budgetName", bundle.getString("id", ""));
        assertEquals(true, bundle.getBoolean("update", false));
    }
}