package performa.utils;

import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.Card;
import com.stripe.model.Coupon;
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 com.stripe.model.UsageRecord;
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 java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import oneit.appservices.config.ConfigMgr;
import oneit.logging.LogLevel;
import oneit.logging.LogMgr;
import oneit.logging.LoggingArea;
import oneit.objstore.ObjectTransaction;
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.HiringTeam;
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(HiringTeam hiringTeam) throws FieldException
    {
        try 
        {
            SecUser             secUser         =   hiringTeam.getAddedByUser().getUser();
            Map<String, Object> customerParams  =   new HashMap<>();

            customerParams.put("description", hiringTeam);
            customerParams.put("email", secUser.getEmail());
            
            Customer    customer    =   Customer.create(customerParams);
            
            hiringTeam.setStripeReference(customer.getId());
            
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, "Create customer in stripe : ", customer);
        } 
        catch (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating a customer in stripe");
        }
    }
    
    
    public static Card updateCardDetails(HiringTeam hiringTeam, String token) throws FieldException
    {
        try 
        {
            if(hiringTeam.getStripeReference() == null)
            {
                createCustomer(hiringTeam.getAddedByUser().getDefaultHiringTeam());
            }
            
            Customer customer = Customer.retrieve(hiringTeam.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 (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while updating a customer in stripe");
        }
        
        return null;
    }
    
    
    public static Card retrieveCard(HiringTeam hiringTeam) throws FieldException
    {
        try 
        {
            Customer customer = Customer.retrieve(hiringTeam.getStripeReference());
            
            return (Card) customer.getSources().retrieve(customer.getDefaultSource());
        } 
        catch (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while updating a customer in stripe");
        } 
        
        return null;
    }
    
    public static Coupon retrieveCoupon(String couponCode) throws FieldException
    {
        try 
        {
            return Coupon.retrieve(couponCode);
        } 
        catch (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while updating a customer in stripe");
        } 
        
        return null;
    }
    
    public static Subscription retrieveSubscription(String subscriptionRef) throws FieldException
    {
        try 
        {
            // Subscription subscription = Subscription.retrieve(subscriptionRef);
            // new Date(subscription.getCurrentPeriodEnd() * 1000)
            // new Date(subscription.getCurrentPeriodStart()* 1000)
            return Subscription.retrieve(subscriptionRef);
        } 
        catch (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while retrieving a subscription 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 (StripeException 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().intValue());
            
            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 (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating subscrition in stripe");
        } 
    }
    
    
    public static void updatePlan(HiringTeam hiringTeam) throws FieldException
    {
        try 
        {
            Subscription    subscription    =   null;
            PaymentPlan     paymentPlan     =   hiringTeam.getPaymentPlan();
            
            Map<String, Object> itemA   =   new HashMap<>();
            Map<String, Object> itemB   =   new HashMap<>();
            
            if(hiringTeam.getStripeSubscription() != null)
            {
                subscription    =   Subscription.retrieve(hiringTeam.getStripeSubscription());
                String  subID   =   subscription.getSubscriptionItems().getData().get(0).getId();  
                
                itemA.put("id", subID);
                itemB.put("id", subID);
            }
            
            itemA.put("plan", paymentPlan.getStripeReference());
            itemB.put("plan", paymentPlan.getLinkedPlanReference());

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

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

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

            hiringTeam.setStripeSubscription(subscription.getId());
        } 
        catch (StripeException ex) 
        {
            LogMgr.log(LoggingArea.ALL, LogLevel.PROCESSING1, ex, "Error while creating subscrition in stripe");
        } 
    }
    
    
    public static void recordUsage(HiringTeam hiringTeam)
    {
        try 
        {
            Map<String, Object> params          =   new HashMap<>();
            
            Date    now             =   new Date();
            String  subscription    =   hiringTeam.getManageOwnBilling() ? hiringTeam.getStripeSubscription() : hiringTeam.getBillingTeam().getStripeSubscription();

            params.put("quantity", 1);
            params.put("timestamp", now.getTime() / 1000L);
            params.put("subscription_item", subscription);
            params.put("action", "increment");

            UsageRecord.create(params, null);
        } 
        catch (StripeException ex) 
        {
            Logger.getLogger(StripeUtils.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    
    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");
        }
    }
}
