/* 
 * Copyright 2010-2011 Ning, Inc.
 *
 * Ning licenses this file to you 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.ning.billing.entitlement.api.timeline;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import org.joda.time.DateTime;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.SubscriptionTransitionType;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.timeline.BundleTimeline;
import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;

public class TestRepairWithError extends TestApiBaseRepair {

    private static final String baseProduct = "Shotgun";
    private TestWithException test;
    private Subscription baseSubscription;
    private DateTime startDate;
    @Override
    public Injector getInjector() {
        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
    }


    @BeforeMethod(groups={"fast"})
    public void beforeMethod() throws Exception {
        test = new TestWithException();
        startDate = clock.getUTCNow();
        baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
        testListener.reset();
        clock.resetDeltaFromReality();
    }
  
    @Test(groups={"fast"})
    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException {

                // MOVE AFTER TRIAL
                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
                Duration durationShift = getDurationDay(40);
                clock.setDeltaFromReality(durationShift, 0);
                assertTrue(testListener.isApiCompleted(5000));
                
                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
                
                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<DeletedEvent>emptyList(), Collections.singletonList(ne));
                
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));

                repairApi.repairBundle(bRepair, true, context);
            }
        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING);
    }
    
    @Test(groups={"fast"})
    public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {

                Duration durationShift = getDurationDay(3);
                clock.setDeltaFromReality(durationShift, 0);
                
                testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
                DateTime changeTime = clock.getUTCNow();
                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
                assertTrue(testListener.isApiCompleted(5000));
                
                // MOVE AFTER TRIAL
                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
                durationShift = getDurationDay(40);
                clock.addDeltaFromReality(durationShift);
                assertTrue(testListener.isApiCompleted(5000));
                
                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
                DeletedEvent de = createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId());                

                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));

                repairApi.repairBundle(bRepair, true, context);
            }
        }, ErrorCode.ENT_REPAIR_INVALID_DELETE_SET);
    }

    @Test(groups={"fast"})
    public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException {
                
                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
                DeletedEvent de = createDeletedEvent(UUID.randomUUID());
                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
                
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));

                repairApi.repairBundle(bRepair, true, context);
            }
        }, ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT);
    }
    
    @Test(groups={"fast"})
    public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException {
                
                // MOVE AFTER TRIAL
                   testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
                   Duration durationShift = getDurationDay(40);
                   clock.setDeltaFromReality(durationShift, 0);
                   assertTrue(testListener.isApiCompleted(5000));
                   
                   BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                   sortEventsOnBundle(bundleRepair);
                   PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
                   NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, baseSubscription.getStartDate().plusDays(10), spec);
                   List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
                   des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                
                   SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
                   
                   BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));

                   repairApi.repairBundle(bRepair, true, context);
                
            }
        }, ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY);
    }

    @Test(groups={"fast"})
    public void testENT_REPAIR_SUB_EMPTY() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException {
                
             // MOVE AFTER TRIAL
                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
                Duration durationShift = getDurationDay(40);
                clock.setDeltaFromReality(durationShift, 0);
                assertTrue(testListener.isApiCompleted(5000));
                
                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
                
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));

                repairApi.repairBundle(bRepair, true, context);
            }
        }, ErrorCode.ENT_REPAIR_SUB_EMPTY);
    }
    
    @Test(groups={"fast"})
    public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
               

                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
                Duration someTimeLater = getDurationDay(3);
                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);

                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);

                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
                clock.addDeltaFromReality(someTimeLater);

                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                
                // Quick check
                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
                assertEquals(bpRepair.getExistingEvents().size(), 2);
                
                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
                assertEquals(aoRepair.getExistingEvents().size(), 2);
                

                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));        
                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));

                DateTime aoRecreateDate = aoSubscription.getStartDate().minusDays(5);
                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
                
                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
                
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
                
                boolean dryRun = true;
                repairApi.repairBundle(bRepair, dryRun, context);
            }
        }, ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START);
    }
    
    @Test(groups={"fast"})
    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
                

                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
                Duration someTimeLater = getDurationDay(3);
                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);

                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);

                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
                clock.addDeltaFromReality(someTimeLater);

                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                
                // Quick check
                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
                assertEquals(bpRepair.getExistingEvents().size(), 2);
                
                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
                assertEquals(aoRepair.getExistingEvents().size(), 2);
                

                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
                //des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
                DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(10);
                
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
                
                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
                
                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
                
                boolean dryRun = true;
                repairApi.repairBundle(bundleRepair, dryRun, context);
            }
        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING);
    }


    @Test(groups={"fast"})
    public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {

              //testListener.pushExpectedEvent(NextEvent.PHASE);

                clock.setDeltaFromReality(getDurationDay(5), 0);
                //assertTrue(testListener.isCompleted(5000));

                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
                
                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                
                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);

                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);

                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));

                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
                
                // FIRST ISSUE DRY RUN
                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
                
                boolean dryRun = true;
                repairApi.repairBundle(bRepair, dryRun, context);
            }
        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO);
    }
    
    //
    // CAN'T seem to trigger such case easily, other errors trigger before...
    //
    @Test(groups={"fast"}, enabled=false)
    public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
                /*
              //testListener.pushExpectedEvent(NextEvent.PHASE);

                clock.setDeltaFromReality(getDurationDay(5), 0);
                //assertTrue(testListener.isCompleted(5000));

                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
                
                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                
                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);

                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);

                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
                List<DeletedEvent> des = new LinkedList<SubscriptionRepair.DeletedEvent>();
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));

                SubscriptionRepair bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
                
                ne = createNewEvent(SubscriptionTransitionType.CANCEL, clock.getUTCNow().minusDays(1),  null);
                SubscriptionRepair aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
                
                
                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
                allRepairs.add(bpRepair);
                allRepairs.add(aoRepair);
                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
                // FIRST ISSUE DRY RUN
                BundleRepair bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
                
                boolean dryRun = true;
                repairApi.repairBundle(bRepair, dryRun, context);
                */
            }
        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE);
    }
    
    @Test(groups={"fast"}, enabled=false)
    public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception {
        test.withException(new TestWithExceptionCallback() {
            @Override
            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {

                
                /*
                // MOVE CLOCK -- JUST BEFORE END OF TRIAL
                clock.setDeltaFromReality(getDurationDay(29), 0);
                
                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
                
                // MOVE CLOCK -- RIGHT OUT OF TRIAL
                testListener.pushExpectedEvent(NextEvent.PHASE);                
                clock.addDeltaFromReality(getDurationDay(5));
                assertTrue(testListener.isCompleted(5000));

                DateTime requestedChange = clock.getUTCNow();
                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);

                DateTime reapairTime = clock.getUTCNow().minusDays(1);

                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
                sortEventsOnBundle(bundleRepair);
                
                SubscriptionRepair bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
                SubscriptionRepair aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);

                List<DeletedEvent> bpdes = new LinkedList<SubscriptionRepair.DeletedEvent>();
                bpdes.add(createDeletedEvent(bpRepair.getExistingEvents().get(2).getEventId()));    
                bpRepair = createSubscriptionReapir(baseSubscription.getId(), bpdes, Collections.<NewEvent>emptyList());
                
                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, reapairTime, null);
                aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
                
                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
                allRepairs.add(bpRepair);
                allRepairs.add(aoRepair);
                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
                
                boolean dryRun = false;
                repairApi.repairBundle(bundleRepair, dryRun, context);
                */
                }
        }, ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT);
    }

}
