package performa.utils;

import com.stripe.Stripe;
import com.stripe.exception.APIConnectionException;
import com.stripe.exception.APIException;
import com.stripe.exception.AuthenticationException;
import com.stripe.exception.CardException;
import com.stripe.exception.InvalidRequestException;
import com.stripe.model.Card;
import com.stripe.model.Customer;
import com.stripe.model.Event;
import com.stripe.model.Invoice;
import com.stripe.model.Plan;
import com.stripe.model.Subscription;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import oneit.appservices.config.ConfigMgr;
import oneit.logging.LogLevel;
import oneit.logging.LogMgr;
import oneit.logging.LoggingArea;
import oneit.objstore.ObjectTransaction;
import oneit.objstore.StorageException;
import oneit.objstore.rdbms.filters.EqualsFilter;
import oneit.security.SecUser;
import oneit.utils.DateDiff;
import oneit.utils.parsers.FieldException;
import performa.orm.Company;
import performa.orm.CompanyUser;
import performa.orm.PaymentPlan;
import spark.utils.IOUtils;


public class StripeUtils 
{
    public static final String  STRIPE_KEY          =   ConfigMgr.getKeyfileString("stripe.key","");
    public static final String  STRIPE_PUB_KEY      =   ConfigMgr.getKeyfileString("stripe.pubkey","");
    public static final String  STRIPE_PLAN_ID      =   ConfigMgr.getKeyfileString("stripe.plan.id","0001");
    public static final String  STRIPE_COUPON_ID    =   ConfigMgr.getKeyfileString("stripe.coupon.id","EAP");
    
    static
    {
        Stripe.apiKey   =   STRIPE_KEY;
    }
    
    
    public static void createCustomer(CompanyUser companyUser) throws FieldException
    {
        try 
        {
            Company             company         =   companyUser.getCompany();
            SecUser             secUser         =   companyUser.getUser();
            Map<String, Object> customerParams  =   new HashMap<>();

            customerParams.put("description", company);
            customerParams.put("email", secUser.getEmail());
            
            Customer    customer    =   Customer.create(customerParams);
            
            company.setStripeReference(customer.getId());
            
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Create customer in stripe : ", customer);
        } 
        catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating a customer in stripe");
        } 
    }
    
    
    public static Card updateCardDetails(Company company, String token) throws FieldException
    {
        try 
        {
            if(company.getStripeReference() == null)
            {
                createCustomer(company.getAddedByUser());
            }
            
            Customer customer = Customer.retrieve(company.getStripeReference());
            
            Map<String, Object> updateParams = new HashMap<>();
            
            updateParams.put("source", token);

            customer    =   customer.update(updateParams);
            
            Card    card    =   (Card) customer.getSources().retrieve(customer.getDefaultSource());
            
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Update card details in stripe, customer  : ", customer, " card : ", card);

            return card;
            
        } 
        catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while updating a customer in stripe");
        } 
        
        return null;
    }
    
    
    public static Card retrieveCard(Company company) throws FieldException
    {
        try 
        {
            Customer customer = Customer.retrieve(company.getStripeReference());
            
            return (Card) customer.getSources().retrieve(customer.getDefaultSource());
        } 
        catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while updating a customer in stripe");
        } 
        
        return null;
    }
    
    
    public static List<Invoice> retrieveInvoices(Company company) throws FieldException
    {
        if(company.getStripeSubscription() != null)
        {
            try 
            {
                Map<String, Object> invoiceParams = new HashMap<>();

                invoiceParams.put("customer", company.getStripeReference());

                return Invoice.list(invoiceParams).getData();
            } 
            catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
            {
                LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while retriving invoices in stripe for subscription: " + company.getStripeSubscription());
            } 
        }
        
        return new ArrayList<>();
    }
    
    
    public static void subscribeCustomer(Company company) throws FieldException
    {
        try 
        {
            Plan    plan        =   Plan.retrieve(STRIPE_PLAN_ID);
            Date    today       =   new Date(); 
            Date    trialExpiry =   DateDiff.add(today, Calendar.DATE, plan.getTrialPeriodDays());
            
            Map<String, Object> item    =   new HashMap<>();
            item.put("plan", STRIPE_PLAN_ID);

            Map<String, Object> items   =   new HashMap<>();
            items.put("0", item);

            Map<String, Object> params  =   new HashMap<>();
            params.put("items", items);
            params.put("coupon", STRIPE_COUPON_ID);
            params.put("customer", company.getStripeReference());
            params.put("trial_end", trialExpiry.getTime() / 1000L);

            Subscription    subscription    =   Subscription.create(params);
            
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Subscribing customer in stripe  : ", subscription);

            company.setStripeSubscription(subscription.getId());
        } 
        catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating subscrition in stripe");
        } 
    }
    
    
    public static void updatePlan(Company company) throws FieldException
    {
        try 
        {
            Subscription    subscription    =   null;
            PaymentPlan     paymentPlan     =   company.getPaymentPlan();
            
            Map<String, Object> item    =   new HashMap<>();
            
            if(company.getStripeSubscription() != null)
            {
                subscription = Subscription.retrieve(company.getStripeSubscription());

                item.put("id", subscription.getSubscriptionItems().getData().get(0).getId());
            }
            
            item.put("plan", paymentPlan.getStripeReference());

            Map<String, Object> items   =   new HashMap<>();
            items.put("0", item);

            Map<String, Object> params  =   new HashMap<>();
            params.put("items", items);

            if(subscription != null)
            {
                subscription.update(params);
            }
            else 
            {
                params.put("customer", company.getStripeReference());
                
                subscription    =   Subscription.create(params);
            }
            
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Subscribing customer in stripe  : ", subscription);

            company.setStripeSubscription(subscription.getId());
        } 
        catch (StorageException | AuthenticationException | InvalidRequestException | APIConnectionException | CardException | APIException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating subscrition in stripe");
        } 
    }
    
    
    public static void handleWebhook(HttpServletRequest request, ObjectTransaction objTran) throws FieldException 
    {
        try 
        {
            String rawJson = IOUtils.toString(request.getInputStream());
            
            Event event = Event.GSON.fromJson(rawJson, Event.class);
        
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Event received from stripe  : ", event);

            if (event != null && event.getType().equals("invoice.payment_succeeded")) 
            {
                Invoice invoice = (Invoice) event.getData().getObject();

                LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Payment Succeeded event received from stripe with invoice  : ", invoice);
                
                if(invoice.getBilling().equals("charge_automatically"))
                {
                    Company[] companies = Company.SearchByAll().andStripeReference(new EqualsFilter<>(invoice.getCustomer())).search(objTran);

                    if(companies != null && companies.length > 0)
                    {
                        Company company     =   companies[0];
                        Date    invoiceDate =   new Date(invoice.getDate() * 1000L);

                        if(company.getPlanRenewedOn() == null || !DateDiff.startOfDay(invoiceDate).equals(DateDiff.startOfDay(company.getPlanRenewedOn())))
                        {
                            if(company.getPlanRenewedOn() != null)
                            {
                                company.setUsedCredits(0);
                            }
                            
                            company.setPlanRenewedOn(invoiceDate);
                        }
                        
                        LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Setting company with reset plan details  : ", company);
                    }
                }
            }
        } 
        catch (IOException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while handling webhook");
        }
    }
}
