<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Stripe extends App_Controller
{
    protected $subscriptionMetaKey = 'pcrm-subscription-hash';

    /**
     * Create the application Stripe webhook endpoint
     *
     * @return mixed
     */
    public function create_webhook()
    {
        if (staff_can('edit', 'settings')) {
            $this->load->library('stripe_core');

            try {
                $webhooks = $this->stripe_core->list_webhook_endpoints();

                foreach ($webhooks->data as $webhook) {
                    if ((isset($webhook->metadata->identification_key)
                                            && $webhook->metadata->identification_key == get_option('identification_key'))
                        || $webhook->url == $this->stripe_gateway->webhookEndPoint) {
                        $this->stripe_core->delete_webhook($webhook->id);
                    }
                }

                if ($this->input->get('recreate')) {
                    update_option('stripe_webhook_id', '');
                    update_option('stripe_webhook_signing_secret', '');
                }
            } catch (Exception $e) {
            }

            try {
                $this->stripe_core->create_webhook();
                set_alert('success', _l('webhook_created'));
            } catch (Exception $e) {
                $this->session->set_flashdata('stripe-webhook-failure', $e->getMessage());
            }

            redirect(admin_url('settings/?group=payment_gateways&tab=online_payments_stripe_tab'));
        }
    }

    /**
     * Enable the application Stripe webhook endpoint
     *
     * @return mixed
     */
    public function enable_webhook()
    {
        if (staff_can('edit', 'settings')) {
            $this->load->library('stripe_core');

            $this->stripe_core->enable_webhook(get_option('stripe_webhook_id'));

            redirect(admin_url('settings/?group=payment_gateways&tab=online_payments_stripe_tab'));
        }
    }

    /**
     * The application Stripe webhook endpoint
     *
     * @return mixed
     */
    public function webhook_endpoint()
    {
        $this->load->library('stripe_core');
        $this->load->library('stripe_subscriptions');
        $this->load->model('subscriptions_model');

        $payload = @file_get_contents('php://input');
        $event   = null;

        if (! isset($_SERVER['HTTP_STRIPE_SIGNATURE'])) {
            return;
        }

        // Validate the webhook
        try {
            $event = $this->stripe_core->construct_event($payload, get_option('stripe_webhook_signing_secret'));
        } catch (UnexpectedValueException $e) {
            // Invalid payload
            http_response_code(400); // PHP 5.4 or greater

            exit();
        } catch (Stripe\Exception\SignatureVerificationException $e) {
            // Invalid signature
            http_response_code(400); // PHP 5.4 or greater

            exit();
        }

        try {
            // Handle the checkout.session.completed event
            if ($event->type == 'checkout.session.completed') {
                $session = $event->data->object;

                if ($session->payment_intent) {
                    $payment = $this->stripe_core->retrieve_payment_intent($session->payment_intent);

                    if (isset($payment->metadata->InvoiceId)) {
                        $this->load->model('invoices_model');

                        $invoice = $this->invoices_model->get(
                            $payment->metadata->InvoiceId,
                            // 1 Stripe account, multiple CRM installations
                            isset($payment->metadata->InvoiceHash) ? [
                                'hash' => $payment->metadata->InvoiceHash,
                            ] : []
                        );

                        if ($invoice) {
                            $payment_attempt_reference = $payment->metadata->payment_attempt_reference ?? null;
                            $this->load->model('payments_model');
                            if (! $this->payments_model->transaction_exists($payment->id, $invoice->id)) {
                                $this->stripe_gateway->addPayment([
                                    'amount'                    => (strcasecmp($invoice->currency_name, 'JPY') == 0 ? $payment->amount : $payment->amount / 100),
                                    'invoiceid'                 => $invoice->id,
                                    'transactionid'             => $payment->id,
                                    'payment_attempt_reference' => $payment_attempt_reference,
                                ]);
                            }

                            if (! $this->stripe_gateway->is_test()) {
                                $this->db->where('userid', $payment->metadata->ClientId);
                                $this->db->update('clients', ['stripe_id' => $payment->customer]);
                            }
                        }
                    }
                }
            } elseif ($event->type == 'customer.subscription.created') {
                $this->customerSubscriptionCreatedEvent($event);
            } elseif ($event->type == 'invoice.payment_succeeded') {
                $this->invoicePaymentSucceededEvent($event);
            } elseif ($event->type == 'invoice.payment_failed') {
                $this->invoicePaymentFailedEevent($event);
            } elseif ($event->type == 'invoice.payment_action_required') {
                $this->invoicePaymentActionRequiredEevent($event);
            } elseif ($event->type == 'customer.subscription.deleted') {
                $this->customerSubscriptionDeletedEvent($event);
            } elseif ($event->type == 'customer.subscription.updated') {
                $this->customerSubscriptionUpdatedEvent($event);
            } elseif ($event->type == 'customer.deleted') {
                $this->customerDeletedEvent($event);
            }
        } catch (Exception $e) {
            log_activity('Stripe webhook error: ' . $e->getMessage());
        }
    }

    /**
     * After stripe checkout succcess
     * Used only to display success message to the customer
     *
     * @param string $invoice_id   The invoice id the payment is made to
     * @param strgin $invoice_hash invoice hash
     *
     * @return mixed
     */
    public function success($invoice_id, $invoice_hash)
    {
        set_alert('success', _l('online_payment_recorded_success'));

        redirect(site_url('invoice/' . $invoice_id . '/' . $invoice_hash));
    }

    /**
     * Handle subscription created event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function customerSubscriptionCreatedEvent($event)
    {
        $subscription = $event->data->object;

        if (isset($subscription->metadata[$this->subscriptionMetaKey])) {
            $subscription   = $this->stripe_subscriptions->get_subscription($subscription->id);
            $dbSubscription = $this->subscriptions_model->get_by_hash($subscription->metadata[$this->subscriptionMetaKey]);
            $update         = ['in_test_environment' => $this->stripe_gateway->is_test() ? 1 : 0];

            $subscription = $this->stripe_subscriptions->get_subscription($subscription->id);

            $this->stripe_core->update_customer($subscription->customer, [
                'invoice_settings' => [
                    'default_payment_method' => $subscription->default_payment_method,
                ],
            ]);

            Stripe\Subscription::update($subscription->id, ['default_payment_method' => '']);

            if (! empty($dbSubscription->date)) {
                if ($dbSubscription->date <= date('Y-m-d')) {
                    // Updates the first billing date to be today because
                    // in the create_subscription_data method is now
                    $update['date'] = date('Y-m-d');
                }

                if ($dbSubscription->date > date('Y-m-d')) {
                    $update['status']                 = 'future';
                    $update['next_billing_cycle']     = strtotime($dbSubscription->date);
                    $update['stripe_subscription_id'] = $subscription->id;
                    $update['date_subscribed']        = date('Y-m-d H:i:s');
                }
            }

            $this->subscriptions_model->update($dbSubscription->id, $update);

            if (! $this->stripe_gateway->is_test()) {
                $this->db->where('userid', $dbSubscription->clientid);
                $this->db->update('clients', ['stripe_id' => $subscription->customer]);
            }

            send_email_customer_subscribed_to_subscription_to_staff($dbSubscription);

            hooks()->do_action('customer_subscribed_to_subscription', $dbSubscription);
        }
    }

    /**
     * Handle subscription invoice payment succeded event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function invoicePaymentSucceededEvent($event)
    {
        $crmSubscriptionItem = null;
        $invoice             = $event->data->object;

        // Let's check if it's really subscription created from CRM
        foreach ($invoice->lines->data as $item) {
            if (isset($item->metadata[$this->subscriptionMetaKey])) {
                $crmSubscriptionItem = $item;

                break;
            }
        }

        if (! is_null($crmSubscriptionItem)) {
            $invoicePayments = Stripe\InvoicePayment::all([
                'invoice' => $invoice->id,
                'status'  => 'paid',
            ]);

            $dbSubscription = $this->subscriptions_model->get_by_hash($crmSubscriptionItem->metadata[$this->subscriptionMetaKey]);

            if ($dbSubscription) {
                $this->subscriptions_model->update($dbSubscription->id, ['next_billing_cycle' => $crmSubscriptionItem->period->end]);
                $this->load->model('payments_model');

                $new_invoice_data = create_subscription_invoice_data($dbSubscription, $invoice);
                $this->load->model('invoices_model');

                if (! defined('STRIPE_SUBSCRIPTION_INVOICE')) {
                    define('STRIPE_SUBSCRIPTION_INVOICE', true);
                }

                $id = $this->invoices_model->add($new_invoice_data);

                if ($id) {
                    $this->db->where('id', $id);
                    $this->db->update(db_prefix() . 'invoices', [
                        'addedfrom' => $dbSubscription->created_from,
                    ]);

                    // Probably created via the checkout.session.completed event type
                    foreach ($invoicePayments->data as $invoicePayment) {
                        if (! $this->payments_model->transaction_exists($invoicePayment->payment->payment_intent)) {
                            $payment_data['invoiceid']     = $id;
                            $payment_data['paymentmode']   = 'stripe';
                            $payment_data['amount']        = strcasecmp($dbSubscription->currency_name, 'JPY') == 0 ? $invoicePayment->amount_paid : $invoicePayment->amount_paid / 100;
                            $payment_data['transactionid'] = $invoicePayment->payment->payment_intent;

                            $this->load->model('payments_model');
                            $this->payments_model->add($payment_data, $dbSubscription->id);
                        }
                    }

                    $update = [
                        'status'                 => 'active',
                        'stripe_subscription_id' => $invoice->parent->subscription_details->subscription,
                    ];

                    // In case updated previously in subscription.created event and the subscription was in future
                    if (empty($dbSubscription->date_subscribed)) {
                        $update['date_subscribed'] = date('Y-m-d H:i:s');
                    }

                    if (empty($dbSubscription->date)) {
                        $update['date'] = date('Y-m-d');
                    }

                    $this->subscriptions_model->update($dbSubscription->id, $update);
                }
            }
        }
    }

    /**
     * Handle subscription invoice payment failed event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function invoicePaymentFailedEevent($event)
    {
        $invoice = $event->data->object;
        if (isset($invoice->lines->data[0]->metadata[$this->subscriptionMetaKey])) {
            $dbSubscription = $this->subscriptions_model->get_by_hash($invoice->lines->data[0]->metadata[$this->subscriptionMetaKey]);

            if ($dbSubscription) {
                $payment_intent = $this->stripe_core->retrieve_payment_intent($invoice->payment_intent);
                // log_message('error', json_encode($payment_intent, JSON_PRETTY_PRINT));
                // Will handle requires action in the event invoice.payment_action_required
                if ($payment_intent->status != 'requires_action') {
                    $this->subscriptions_model->send_email_template(
                        $dbSubscription->id,
                        $this->getStaffCCForMailTemplate($dbSubscription->created_from),
                        'subscription_payment_failed_to_customer'
                    );
                }
            }
        }
    }

    /**
     * Handle subscription invoice payment require action event event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function invoicePaymentActionRequiredEevent($event)
    {
        $invoice = $event->data->object;
        if (isset($invoice->lines->data[0]->metadata[$this->subscriptionMetaKey])) {
            // Customer was on session while trying to subscribe to the invoice
            // In this case, in the Subscription.php class he will be redirected to the
            // invoice hosted url to confirm the payment
            // he already know that he need to confir the payment and no email is needed
            // perhaps Stripe will send one if configured
            if (isset($invoice->lines->data[0]->metadata['customer-on-session'])) {
                return;
            }

            $dbSubscription = $this->subscriptions_model->get_by_hash($invoice->lines->data[0]->metadata[$this->subscriptionMetaKey]);

            if ($dbSubscription) {
                $contact = $this->clients_model->get_contact(get_primary_contact_user_id($dbSubscription->clientid));

                if (! $contact) {
                    return false;
                }

                send_mail_template(
                    'subscription_payment_requires_action',
                    $dbSubscription,
                    $contact,
                    $invoice->hosted_invoice_url,
                    $this->getStaffCCForMailTemplate($dbSubscription->created_from)
                );
            }
        }
    }

    /**
     * Handle subscription updated event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function customerSubscriptionUpdatedEvent($event)
    {
        $subscription = $event->data->object;
        if (isset($subscription->metadata[$this->subscriptionMetaKey])) {
            $dbSubscription = $this->subscriptions_model->get_by_hash($subscription->metadata[$this->subscriptionMetaKey]);

            if ($dbSubscription) {
                $update = [
                    // in case not yet updated e.q. because of hook or event handler failure
                    'stripe_subscription_id' => $subscription->id,
                    'status'                 => $subscription->status,
                    'next_billing_cycle'     => $subscription->items->data[0]->current_period_end,
                    'quantity'               => $subscription->items->data[0]->quantity,
                    'ends_at'                => $subscription->cancel_at_period_end ? $subscription->cancel_at : null,
                ];

                if ($dbSubscription->status == 'future') {
                    unset($update['status'], $update['next_billing_cycle']);
                }

                $this->subscriptions_model->update($dbSubscription->id, $update);
            }
        }
    }

    /**
     * Handle subscription deleted event
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function customerSubscriptionDeletedEvent($event)
    {
        $subscription = $event->data->object;
        if (isset($subscription->metadata[$this->subscriptionMetaKey])) {
            $dbSubscription = $this->subscriptions_model->get_by_hash($subscription->metadata[$this->subscriptionMetaKey]);

            if ($dbSubscription) {
                $this->subscriptions_model->send_email_template(
                    $dbSubscription->id,
                    $this->getStaffCCForMailTemplate($dbSubscription->created_from),
                    'subscription_cancelled_to_customer'
                );

                $this->subscriptions_model->update(
                    $dbSubscription->id,
                    ['status' => $subscription->status, 'next_billing_cycle' => null]
                );
            }
        }
    }

    /**
     * Handle customer deleted
     *
     * @param stdClass $event
     *
     * @return void
     */
    protected function customerDeletedEvent($event)
    {
        $stripeClient = $event->data->object;
        $this->db->where('stripe_id', $stripeClient->id);
        $client = $this->db->get('clients')->row();

        if ($client) {
            $this->db->where('userid', $client->userid)->update('clients', ['stripe_id' => null]);
        }
    }

    /**
     * Get CC for the subscription mail template
     *
     * @param int $staff_id
     *
     * @return string
     */
    protected function getStaffCCForMailTemplate($staff_id)
    {
        $this->db->select('email')
            ->from(db_prefix() . 'staff')
            ->where('staffid', $staff_id);
        $staff = $this->db->get()->row();

        return $staff ? $staff->email : '';
    }
}
