<?php

/**
 * @title Self-Service Store
 * @desc users purchase credits and can spend it to
 * other products and content
 */

class Bootstrap_SelfService extends Am_Module
{
    const ADMIN_PERM_CONFIG_ID = 'self-service-config';
    const ADMIN_PERM_ID = 'self-service';
    const EVENT_SELF_SERVICE_THANKS_REDIRECT = 'selfServiceThanksRedirect';

    function onSetupEmailTemplateTypes(Am_Event $e)
    {
        $e->addReturn([
            'id' => 'self-service.product_added_admin',
            'title' => 'Self Service User Get Product',
            'mailPeriodic' => Am_Mail::REGULAR,
            'isAdmin' => true,
            'vars' => [
                'user',
                'product.title' => ___('Product Title'),
            ],
        ], 'self-service.product_added_admin');

        $e->addReturn([
            'id' => 'self-service.credits_received',
            'title' => 'Credits transfer notification for recipient',
            'mailPeriodic' => Am_Mail::USER_REQUESTED,
            'vars' => [
                'user',
                'sender' => ___('Sender'),
                'amount' => ___('Amount')
            ],
        ], 'self-service.credits_received');

    }

    public function onGridUserInitGrid(Am_Event_Grid $e)
    {
        $e->getGrid()->actionAdd(new Am_Grid_Action_Group_SelfserviceCredit(true));
        $e->getGrid()->actionAdd(new Am_Grid_Action_Group_SelfserviceCredit(false));
    }

    public function onAdminMenu(Am_Event $e)
    {
        $controller = $this->getDi()->productTable->countBy() > (1 + $this->getDi()->config->get('admin.records-on-page', 10)) ? 'admin-products-grid' : 'admin-products';

        $menu = $e->getMenu();
        $menu->addPage([
            'id' => 'self-service',
            'controller' => $controller,
            'module' => 'self-service',
            'label' => ___('Self-Service Store'),
            'resource' => self::ADMIN_PERM_CONFIG_ID,
        ]);
    }

    function onWidgetGetTargetList(Am_Event $e)
    {
        $_= $e->getReturn();
        $_['Self Service Store'] = [
            'self-service/top' => 'Products List (Top)',
            'self-service/bottom' => 'Products List (Bottom)',
            'self-service/transfer/top' => 'Credits Transfer (Top)',
        ];
        $e->setReturn($_);
    }

    public function onWidgetBeforeRender(Am_Event $e)
    {
        $e->getTemplate()->assign('selfservice_balance', $this->getDi()->selfserviceBalanceTable->balance($e->getUser()));
    }

    public function onProductAfterDelete(Am_Event $e)
    {
        $product = $e->getProduct();
        $this->getDi()->selfserviceTypeProductTable->deleteByProductId($product->pk());
    }

    public function onInvoiceStarted(Am_Event_InvoiceStarted $event)
    {
        $invoice = $event->getInvoice();
        // Do not add credits if rebilling was restored.
        if (!(float) $invoice->first_total && !$invoice->data()->get(Invoice::ORIG_ID))
            $this->onPaymentWithAccessAfterInsert($event);
    }

    public function onPaymentWithAccessAfterInsert($event)
    {
        $invoice = $event->getInvoice();
        $payment = $event->getPayment();
        $access = $this->getDi()->accessTable->findFirstBy([
                'invoice_payment_id' => $payment ? $payment->pk() : null,
                'invoice_id' => $invoice->pk()
        ]);

        $pc = $invoice->getPaymentsCount();
        if ($pc == 0 || ((float) $invoice->first_total && $pc == 1)) {
            $field = 'value';
        } else {
            $field = 'value_second';
        }
        foreach ($invoice->getItems() as $item) {
            if ($field == 'value_second' && !$item->second_period) {
                continue;
            }
            foreach ($this->getDi()->selfserviceTypeProductTable->findBy([
                'billing_plan_id' => $item->billing_plan_id,
                'relation' => SelfserviceTypeProduct::IN,
            ]) as $stp) {
                switch ($stp->expire) {
                    case 'lifetime' :
                        $expire = Am_Period::MAX_SQL_DATE;
                        break;
                    case 'product' :
                        $expire =  $access->expire_date;
                        break;
                    default:
                        $period = new Am_Period($stp->expire);
                        $expire = $period->addTo($this->getDi()->sqlDate);
                }
                /* @var $stp SelfserviceTypeProduct */
                $this->getDi()->selfserviceHistoryTable->credit($invoice->getUser(),
                    $stp->{$field} * $item->qty,
                    $stp->type_id, ___("Product purchased: %s%s", $item->item_title, $item->qty > 1 ? " ({$item->qty}pcs)" : ''),
                    $pc == 0 ? 'invoice' : 'invoice_payment',
                    $pc == 0 ? $invoice->pk() : $event->getPayment()->pk(), $expire);
            }
        }
    }

    function onRefundAfterInsert(Am_Event $e)
    {
        $refund = $e->getRefund();
        if (empty($refund->invoice_payment_id)) return;
        $payment = $this->getDi()->invoicePaymentTable->load($refund->invoice_payment_id);

        $r = $this->getDi()->selfserviceHistoryTable->findFirstBy([
                'table' => 'invoice_payment',
                'record_id' => $payment->pk()
        ]);

        if ($r) {
            $this->getDi()->selfserviceHistoryTable->refund($e->getUser(), $r->value, $r->type_id,
                ___('Refund (%s/%s)', $payment->receipt_id, $refund->receipt_id),
                'invoice_refund', $refund->pk());
        }
    }

    function onGetPermissionsList(Am_Event $e)
    {
        $e->addReturn("Self-Service: Can manage store", self::ADMIN_PERM_CONFIG_ID);
        $e->addReturn("Self-Service: Can manage balance and users", self::ADMIN_PERM_ID);
    }

    function onUserTabs(Am_Event_UserTabs $e)
    {
        if ($e->getUserId() > 0) {
            $e->getTabs()->addPage([
                'id' => 'self-service-user-tab',
                'uri' => '#',
                'label' => ___('Self-Service'),
                'order' => 1100,
                'resource' => self::ADMIN_PERM_ID,
                'module' => 'self-service',
                'controller' => 'admin',
                'action' => 'info',
                'params' => [
                    'user_id' => $e->getUserId(),
                ],
            ]);
        }
    }

    function onUserMenuItems(Am_Event $e)
    {
        $e->addReturn([$this, 'buildMenu'], $this->getId());
    }

    function buildMenu(Am_Navigation_Container $nav, /* ?User */ $user, $order, $config)
    {
        if (!$user) return $nav;

        $subpages = [];
        $options = $this->getAvailableTypeOptions($user);
        foreach ($options as $type_id => $title) {
            $subpages[] = [
                'id' => 'self-service-' . $type_id,
                'controller' => 'index',
                'module' => 'self-service',
                'params' => [
                    'type_id' => $type_id,
                ],
                'label' => $title,
                'order' => $order,
            ];
            if($this->getConfig('allow-transfer'))
            {
                $subpages[] = [
                    'id' => 'self-service-transfer-' . $type_id,
                    'controller' => 'index',
                    'action' => 'transfer',
                    'module' => 'self-service',
                    'params' => [
                        'type_id' => $type_id,
                    ],
                    'label' => ___('Transfer Credits'),
                    'order' => $order,
                ];

            }

        }
        switch (count($subpages)) {
            case 0:
                break;
            case 1:
                return $nav->addPage(array_pop($subpages), true);
            default:
                return $nav->addPage(
                    [
                        'id' => 'self-service',
                        'uri' => 'javascript:;',
                        'label' => ___('Self-Service Store'),
                        'order' => $order,
                        'pages' => $subpages
                    ], true);
        }
    }

    function onLoadReports()
    {
        class_exists('Am_Report_SelfService', true);
    }

    function onInitFinished(Am_Event $e)
    {
        $this->getDi()->router->addRoute(
            'self-service-thanks',
            new Am_Mvc_Router_Route('self-service/thanks', [
                'module' => 'self-service',
                'controller' => 'index',
                'action' => 'thanks',
            ])
        );
    }

    function getAvailableTypeOptions(User $user)
    {
        $options = $this->getDi()->selfserviceTypeTable->getOptions();
        foreach ($options as $k => $v) {
            if (!$this->getDi()->selfserviceBalanceTable->findBy(['user_id'=>$user->pk(), 'type_id'=>$k])) {
                unset($options[$k]);
            }
        }
        return $options;
    }

    function getAvailableProducts(User $user, $type_id, $cat_id = null)
    {
        $res = [];
        if ($cat_id) {
            $_ = $this->getDi()->productCategoryTable->getCategoryProducts();
            $pids = $_[$cat_id];
            $pids[] = -1;
        }
        $multiWhere = $this->getConfig('multi')? '' :' AND us.user_status_id IS NULL ';
        foreach ($this->getDi()->db->select(<<<CUT
            SELECT p.*, tp.value as credits FROM ?_product p
                LEFT JOIN ?_selfservice_type_product tp ON p.product_id = tp.product_id
                LEFT JOIN ?_user_status us ON us.user_id = ? AND us.product_id = p.product_id AND us.status = 1
                WHERE tp.type_id=?
                    AND relation=?
        {$multiWhere}
                    {AND p.product_id IN (?a)}
                ORDER BY tp.sort_order
CUT
            , $user->pk(), $type_id, SelfserviceTypeProduct::OUT, ($cat_id ? $pids : DBSIMPLE_SKIP)) as $product) {
            $res[] = $this->getDi()->productRecord->fromRow($product);
        }
        return $res;
    }

    function getProduct($product_id, $type_id)
    {
        $product = $this->getDi()->db->selectRow("
            SELECT p.*, tp.value as credits FROM ?_product p
            LEFT JOIN ?_selfservice_type_product tp
                ON p.product_id = tp.product_id
            WHERE tp.type_id=? AND relation=? AND p.product_id=?",
                $type_id, SelfserviceTypeProduct::OUT, $product_id);
        return $product ? $this->getDi()->productRecord->fromRow($product) : null;
    }

//    function onGridContentFilesInitForm(Am_Event_Grid $event)
//    {
//        $event->getGrid()->getForm()->addElement($this->createCreditElement());
//    }
//
//    function onGridContentPagesInitForm(Am_Event_Grid $event)
//    {
//        /* @var $form Am_Form_Admin */
//        $form = $event->getGrid()->getForm();
//
//        $form->insertBefore($this->createCreditElement(),
//                $form->getElementById('meta'))
//            ->setLabel(___("Credits Price\n" .
//                    "if you set up some price in credits for this item it will be available in self-service store and user will be able to get this item for credits"));
//    }
//
//    function onGridContentVideoInitForm(Am_Event_Grid $event)
//    {
//        /* @var $form Am_Form_Admin */
//        $form = $event->getGrid()->getForm();
//
//        $form->insertBefore($this->createCreditElement(),
//                $form->getElementById('meta'))
//            ->setLabel(___("Credits Price\n" .
//                    "if you set up some price in credits for this item it will be available in self-service store and user will be able to get this item for credits"));
//    }
//
//    function onGridContentPagesInitGrid(Am_Event_Grid $event)
//    {
//        /* @var $ds Am_Query */
//        $ds = $event->getGrid()->getDataSource();
//        $ds->leftJoin('?_selfservice_type_resource', 'tr', "t.page_id = tr.resource_id AND tr.resource_type='page'")
//            ->addField('tr.value', 'value');
//
//        $event->getGrid()->addField(new Am_Grid_Field('value', ___('Credits'), true, null, null, '1%'))->setRenderFunction(array($this, 'renderValue'));
//    }
//
//    function onGridContentFilesInitGrid(Am_Event_Grid $event)
//    {
//        /* @var $ds Am_Query */
//        $ds = $event->getGrid()->getDataSource();
//        $ds->leftJoin('?_selfservice_type_resource', 'tr', "t.file_id = tr.resource_id AND tr.resource_type='file'")
//            ->addField('tr.value', 'value');
//
//        $event->getGrid()->addField(new Am_Grid_Field('value', ___('Credits'), true, null, null, '1%'))->setRenderFunction(array($this, 'renderValue'));
//    }
//
//    function onGridContentVideoInitGrid(Am_Event_Grid $event)
//    {
//        /* @var $ds Am_Query */
//        $ds = $event->getGrid()->getDataSource();
//        $ds->leftJoin('?_selfservice_type_resource', 'tr', "t.video_id = tr.resource_id AND tr.resource_type='video'")
//            ->addField('tr.value', 'value');
//
//        $event->getGrid()->addField(new Am_Grid_Field('value', ___('Credits'), true, null, null, '1%'))->setRenderFunction(array($this, 'renderValue'));
//    }
//
//    function onGridContentPagesValuesToForm(Am_Event $event)
//    {
//        $this->valuesToForm($event);
//    }
//
//    function onGridContentPagesAfterSave(Am_Event $event)
//    {
//        $this->afterSave($event);
//    }
//
//    function onGridContentFilesValuesToForm(Am_Event $event)
//    {
//        $this->valuesToForm($event);
//    }
//
//    function onGridContentFilesAfterSave(Am_Event $event)
//    {
//        $this->afterSave($event);
//    }
//
//    function onGridContenVideoValuesToForm(Am_Event $event)
//    {
//        $this->valuesToForm($event);
//    }
//
//    function onGridContentVideoAfterSave(Am_Event $event)
//    {
//        $this->afterSave($event);
//    }

    protected function valuesToForm(Am_Event $event)
    {
        $vars = $event->getArg(0);
        $r = $event->getGrid()->getRecord();
        if ($r->isLoaded() && $tr = $this->getDi()->selfserviceTypeResourceTable->getTypeResource($r)) {
            $vars['_value'] = $tr->value;
            $vars['_type_id'] = $tr->type_id;
            $event->setArg(0, $vars);
        }
    }

    protected function afterSave(Am_Event $event)
    {
        $r = $event->getGrid()->getRecord();
        $vars = $event->getGrid()->getForm()->getValue();
        if (!$vars['_value']) {
            $this->getDi()->selfserviceTypeResourceTable->dropTypeResource($r);
        } else {
            $this->getDi()->selfserviceTypeResourceTable->setTypeResource($r, $vars['_value'], $vars['_type_id']);
        }
    }

    function renderValue(Am_Record $record)
    {
        return '<td align="center">' . ($record->value ? $record->value : '&ndash;') . '</td>';
    }

    protected function createCreditElement()
    {
        $gr = HTML_QuickForm2_Factory::createElement('group');
        $gr->setSeparator(' ')
            ->setLabel(___("Credits Price\n" .
                "if you set up some price in credits for this item it will be available in self-service store and user will be able to get this item for credits"));

        $gr->addText('_value', ['size' => 4]);

        $options = $this->getDi()->selfserviceTypeTable->getOptions();
        $type_id = $gr->addSelect('_type_id')
                ->loadOptions($options)
                ->setValue(key($options));

        if (count($options) == 1) {
            $type_id->toggleFrozen(true);
        }

        return $gr;
    }

    function onAffPayout(Am_Event $event)
    {
        foreach ($event->getPayouts() as $payout) {
            if ($payout->type == 'credits') {
                foreach ($this->getDi()->affPayoutDetailTable->findByPayoutId($payout->pk()) as $d) {
                    $this->getDi()->selfserviceHistoryTable->credit($this->getDi()->userTable->load($d->aff_id),
                        floor($d->amount * $this->getConfig('value', 1)), $this->getConfig('type_id', 1),
                        ___('Commission %s, %s', Am_Currency::render($d->amount), $payout->thresehold_date),
                        'aff_payout_detail', $d->pk());
                    $d->updateQuick('is_paid', 1);
                }
            }
        }
    }

    function onAffGetPayoutOptions(Am_Event $e)
    {
        $e->addReturn('Credits', 'credits');
    }

    function onDbUpgrade(Am_Event $e)
    {
        // Enable rollover for all previous credits and make these credits to be lifetime.
        $this->getDi()->db->query("UPDATE ?_selfservice_type_product SET rollover=1 WHERE rollover IS NULL AND relation = 'in'");
        $this->getDi()->db->query("UPDATE ?_selfservice_type_product SET expire='lifetime' WHERE expire IS NULL AND relation = 'in'");
        $this->getDi()->db->query("UPDATE ?_selfservice_history SET expire = ? WHERE record_type='credit' AND expire IS NULL", Am_Period::MAX_SQL_DATE);
        $this->getDi()->db->query("UPDATE ?_selfservice_type_product SET rollover=2, expire = 'lifetime' WHERE rollover=1 AND relation = 'in'");
        $this->getDi()->db->query("UPDATE ?_selfservice_type_product SET rollover=2, expire = IF(expire = 'lifetime', 'product', expire) WHERE rollover=0 AND relation = 'in'");

    }

    function onDaily(Am_Event $e)
    {
        $res = $this->getDi()->db->queryResultOnly(
            "SELECT user_id, type_id, SUM(`value`) as balance "
            . "FROM ?_selfservice_history "
            . "WHERE record_type='debit' OR (record_type='credit' AND expire < ?) "
            . "GROUP BY user_id, type_id "
            . "HAVING balance > 0", $this->getDi()->sqlDate
            );
        while($row = $this->getDi()->db->fetchRow($res)){
            // If balance>0 correction should be added;
            try
            {
                if($user = $this->getDi()->userTable->load($row['user_id'], false))
                    $this->getDi()->selfserviceHistoryTable->debit(
                        $user,
                        $row['balance'],
                        $row['type_id'],
                        ___('Unused credits expiration'));
            }
            catch(Am_Exception_SelfServiceError $e)
            {
                $this->getDi()->logger->error("self-service Exception", ["exception" => $e]);
            }
        }
    }
}

class Am_Exception_SelfServiceError extends Am_Exception
{

}