/*
 * Copyright 2010-2013 Ning, Inc.
 * Copyright 2014 Groupon, Inc
 * Copyright 2014 The Billing Project, LLC
 *
 * The Billing Project 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 org.killbill.billing.jaxrs;

import java.math.BigDecimal;
import java.sql.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.joda.time.DateTime;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Catalog;
import org.killbill.billing.client.model.Plan;
import org.killbill.billing.client.model.PlanDetail;
import org.killbill.billing.client.model.Product;
import org.killbill.billing.client.model.SimplePlan;
import org.killbill.billing.client.model.Tenant;
import org.killbill.billing.client.model.Usage;
import org.testng.Assert;
import org.testng.annotations.Test;

import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;

public class TestCatalog extends TestJaxrsBase {

    @Test(groups = "slow", description = "Upload and retrieve a per tenant catalog")
    public void testMultiTenantCatalog() throws Exception {
        final String versionPath1 = Resources.getResource("SpyCarBasic.xml").getPath();
        killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
        String catalog = killBillClient.getXMLCatalog(requestOptions);
        Assert.assertNotNull(catalog);
        //
        // We can't deserialize the VersionedCatalog using our JAXB models because it contains several
        // Standalone catalog and ids (JAXB name) are not unique across the various catalogs so deserialization would fail
        //
    }

    @Test(groups = "slow")
    public void testUploadAndFetchUsageCatlog() throws Exception {
        final String versionPath1 = Resources.getResource("UsageExperimental.xml").getPath();
        killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
        String catalog = killBillClient.getXMLCatalog(requestOptions);
        Assert.assertNotNull(catalog);
    }


    @Test(groups = "slow")
    public void testUploadWithErrors() throws Exception {
        final String versionPath1 = Resources.getResource("SpyCarBasic.xml").getPath();
        killBillClient.uploadXMLCatalog(versionPath1, requestOptions);

        // Retry to upload same version
        try {
            killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
            Assert.fail("Uploading same version should fail");
        } catch (KillBillClientException e) {
            Assert.assertEquals(e.getMessage(), "Invalid catalog for tenant : 1");
        }

        // Try to upload another version with an invalid name (different than orignal name)
        try {
            final String versionPath2 = Resources.getResource("SpyCarBasicInvalidName.xml").getPath();
            killBillClient.uploadXMLCatalog(versionPath2, requestOptions);
            Assert.fail("Uploading same version should fail");
        } catch (KillBillClientException e) {
            Assert.assertEquals(e.getMessage(), "Invalid catalog for tenant : 1");
        }

        String catalog = killBillClient.getXMLCatalog(requestOptions);
        Assert.assertNotNull(catalog);
    }

    @Test(groups = "slow", description = "Can retrieve a json version of the catalog")
    public void testCatalog() throws Exception {
        final Set<String> allBasePlans = new HashSet<String>();

        final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(requestOptions);

        Assert.assertEquals(catalogsJson.get(0).getName(), "Firearms");
        Assert.assertEquals(catalogsJson.get(0).getEffectiveDate(), Date.valueOf("2011-01-01"));
        Assert.assertEquals(catalogsJson.get(0).getCurrencies().size(), 3);
        Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 11);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 6);

        for (final Product productJson : catalogsJson.get(0).getProducts()) {
            if (!"BASE".equals(productJson.getType())) {
                Assert.assertEquals(productJson.getIncluded().size(), 0);
                Assert.assertEquals(productJson.getAvailable().size(), 0);
                continue;
            }

            // Save all plans for later (see below)
            for (final Plan planJson : productJson.getPlans()) {
                allBasePlans.add(planJson.getName());
            }

            // Verify Usage info in json
            if (productJson.getName().equals("Bullets")) {
                Assert.assertEquals(productJson.getPlans().get(0).getName(), "bullets-usage-in-arrear");
                Assert.assertEquals(productJson.getPlans().get(0).getPhases().get(0).getType(), "EVERGREEN");
                List<Usage> usages = productJson.getPlans().get(0).getPhases().get(0).getUsages();
                Assert.assertEquals(usages.get(0).getBillingPeriod(), "MONTHLY");
                Assert.assertEquals(usages.get(0).getTiers().get(0).getBlocks().get(0).getUnit(), "bullets");
                Assert.assertEquals(usages.get(0).getTiers().get(0).getBlocks().get(0).getSize(), "100.0");
                Assert.assertEquals(usages.get(0).getTiers().get(0).getBlocks().get(0).getPrices().get(0).getCurrency(), "USD");
                Assert.assertEquals(usages.get(0).getTiers().get(0).getBlocks().get(0).getPrices().get(0).getValue(), 2.95);
            }

            // Retrieve available products (addons) for that base product
            final List<PlanDetail> availableAddons = killBillClient.getAvailableAddons(productJson.getName(), requestOptions);
            final Set<String> availableAddonsNames = new HashSet<String>();
            for (final PlanDetail planDetailJson : availableAddons) {
                availableAddonsNames.add(planDetailJson.getProduct());
            }
            Assert.assertEquals(availableAddonsNames, new HashSet<String>(productJson.getAvailable()));
        }

        // Verify base plans endpoint
        final List<PlanDetail> basePlans = killBillClient.getBasePlans(requestOptions);
        final Set<String> foundBasePlans = new HashSet<String>();
        for (final PlanDetail planDetailJson : basePlans) {
            foundBasePlans.add(planDetailJson.getPlan());
        }
        Assert.assertEquals(foundBasePlans, allBasePlans);
    }

    @Test(groups = "slow", description = "Try to retrieve a json version of the catalog with an invalid date",
            expectedExceptions = KillBillClientException.class,
            expectedExceptionsMessageRegExp = "There is no catalog version that applies for the given date.*")
    public void testCatalogInvalidDate() throws Exception {
        final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(DateTime.parse("2008-01-01"), requestOptions);
        Assert.fail();
    }

    @Test(groups = "slow", description = "Can create a simple Plan into a per-tenant catalog")
    public void testAddSimplePlan() throws Exception {

        killBillClient.addSimplePan(new SimplePlan("foo-monthly", "Foo", ProductCategory.BASE, Currency.USD, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of()), requestOptions);
        List<Catalog> catalogsJson = killBillClient.getJSONCatalog(requestOptions);
        Assert.assertEquals(catalogsJson.size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().get(0).getName(),"Foo");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getName(), "DEFAULT");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().get(0), "foo-monthly");


        killBillClient.addSimplePan(new SimplePlan("foo-annual", "Foo", ProductCategory.BASE, Currency.USD, new BigDecimal("100.00"), BillingPeriod.ANNUAL, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of()), requestOptions);

        catalogsJson = killBillClient.getJSONCatalog(requestOptions);
        Assert.assertEquals(catalogsJson.size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().get(0).getName(),"Foo");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getName(), "DEFAULT");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 2);

    }

    @Test(groups = "slow", description = "Upload and retrieve a per plugin payment state machine config")
    public void testAddSimplePlanWithoutKBDefault() throws Exception {
        // Create another tenant initialized with no default catalog,...
        final Tenant otherTenantNoKBDefault = new Tenant();
        otherTenantNoKBDefault.setApiKey(UUID.randomUUID().toString());
        otherTenantNoKBDefault.setApiSecret(UUID.randomUUID().toString());

        killBillClient.createTenant(otherTenantNoKBDefault, false, requestOptions);

        final RequestOptions requestOptionsOtherTenant = requestOptions.extend()
                                                                       .withTenantApiKey(otherTenantNoKBDefault.getApiKey())
                                                                       .withTenantApiSecret(otherTenantNoKBDefault.getApiSecret())
                                                                       .build();
        // Verify the template catalog is not returned
        List<Catalog> catalogsJson = killBillClient.getJSONCatalog(requestOptionsOtherTenant);
        Assert.assertEquals(catalogsJson.size(), 0);

        killBillClient.addSimplePan(new SimplePlan("foo-monthly", "Foo", ProductCategory.BASE, Currency.USD, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of()), requestOptionsOtherTenant);
        catalogsJson = killBillClient.getJSONCatalog(requestOptionsOtherTenant);
        Assert.assertEquals(catalogsJson.size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().get(0).getName(),"Foo");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getName(), "DEFAULT");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().get(0), "foo-monthly");


        killBillClient.addSimplePan(new SimplePlan("foo-annual", "Foo", ProductCategory.BASE, Currency.USD, new BigDecimal("100.00"), BillingPeriod.ANNUAL, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of()), requestOptionsOtherTenant);

        catalogsJson = killBillClient.getJSONCatalog(requestOptionsOtherTenant);
        Assert.assertEquals(catalogsJson.size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getProducts().get(0).getName(),"Foo");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(),1);
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getName(), "DEFAULT");
        Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 2);
    }



}
