<?php
/**
 * BoxBilling.
 *
 * @copyright BoxBilling, Inc (https://www.boxbilling.org)
 * @license   Apache-2.0
 *
 * Copyright BoxBilling, Inc
 * This source file is subject to the Apache-2.0 License that is bundled
 * with this source code in the file LICENSE
 */

namespace Box\Mod\Servicecentovacast;

class Service implements \Box\InjectionAwareInterface
{
    private $_salt = 'CksDmBH2tgBv1iKsRxcL';

    protected $di;

    public function setDi($di)
    {
        $this->di = $di;
    }

    public function getDi()
    {
        return $this->di;
    }

    public function install()
    {
        $sql = '
        CREATE TABLE IF NOT EXISTS `service_centovacast` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `client_id` bigint(20) DEFAULT NULL,
        `server_id` int(20) DEFAULT NULL,
        `username` varchar(255) DEFAULT NULL,
        `pass` varchar(255) DEFAULT NULL,
        `created_at` varchar(35) DEFAULT NULL,
        `updated_at` varchar(35) DEFAULT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `username` (`username`),
        KEY `client_id_idx` (`client_id`)
        ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
        ';
        $this->di['db']->exec($sql);
    }

    public function toApiArray($row)
    {
        if ($row instanceof \RedBeanPHP\OODBBean) {
            $row = $row->export();
        }

        $row['pass'] = $this->di['crypt']->decrypt($row['pass'], $this->_salt);

        return $row;
    }

    public function getServers()
    {
        $sql = "SELECT id, meta_value
            FROM extension_meta
            WHERE extension = 'mod_servicecentovacast'
            AND meta_key = 'server'
            ORDER BY id DESC
        ";
        $result = $this->di['db']->getAll($sql);
        $servers = [];
        foreach ($result as $s) {
            $d = [];
            $d['id'] = $s['id'];
            $d = array_merge($d, json_decode($s['meta_value'], 1));
            $servers[$s['id']] = $d;
        }

        return $servers;
    }

    public function getServer($id)
    {
        $servers = $this->getServers();
        if (!isset($servers[$id])) {
            throw new \Exception('Centova Cast Server not found');
        }

        if (APPLICATION_ENV == 'testing') {
            $config = [];
            $config['url'] = 'http://castdemo.centova.com:2199';
            $config['secret'] = 'example';
            $config['ip'] = '123.123.123.123';
            $config['hostname'] = 'castdemo.centova.com';

            return $config;
        }

        return $servers[$id];
    }

    /**
     * @param $order
     *
     * @return \RedBeanPHP\OODBBean
     */
    public function create($order)
    {
        $model = $this->di['db']->dispense('service_centovacast');
        $model->client_id = $order->client_id;
        $model->created_at = date('Y-m-d H:i:s');
        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        return $model;
    }

    /**
     * @param $order
     *
     * @return array
     *
     * @see http://www.centova.com/docs/cast/centovacast_xml_api.php?manual=1
     */
    public function activate($order, $model)
    {
        if (!is_object($model)) {
            throw new \Box_Exception('Could not activate order. Service was not created', null, 7456);
        }

        $oc = json_decode($order->config, 1);

        $api = $this->di['api_admin'];
        $client = $api->client_get(['id' => $order->client_id]);
        $product = $api->product_get(['id' => $order->product_id]);
        $pc = $product['config'];

        $required = [
            'server_id' => 'CentovaCast product is not configured properly. Field server_id is missing',
            'maxclients' => 'CentovaCast product is not configured properly. Field maxclients is missing',
            'maxbitrate' => 'CentovaCast product is not configured properly. Field maxbitrate is missing',
            'transferlimit' => 'CentovaCast product is not configured properly. Field transferlimit is missing',
            'diskquota' => 'CentovaCast product is not configured properly. Field diskquota is missing',
            'template' => 'CentovaCast product is not configured properly. Field template is missing',
            'autostart' => 'CentovaCast product is not configured properly. Field autostart is missing',
        ];
        $this->di['validator']->checkRequiredParamsForArray($required, $pc);

        $server = $this->getServer($pc['server_id']);
        $username = (isset($oc['username']) && !empty($oc['username'])) ? $oc['username'] : $this->_genUsername($client);
        $password = (isset($oc['password']) && !empty($oc['password'])) ? $oc['password'] : $this->di['tools']->generatePassword();
        $sourcepassword = $this->di['tools']->generatePassword();

        $params = [
            'hostname' => $server['hostname'],
            'ipaddress' => $server['ip'],
            'port' => 'auto',
            'maxclients' => $pc['maxclients'],
            'username' => $username,
            'adminpassword' => $password,
            'sourcepassword' => $sourcepassword,
            'maxbitrate' => $pc['maxbitrate'],
            'transferlimit' => $pc['transferlimit'],
            'diskquota' => $pc['diskquota'],
            'title' => $client['company'],
            'organization' => $client['company'],
            'genre' => $client['company'],
            'email' => $client['email'],
            'url' => BB_URL,
            'usesource' => '1',
            'introfile' => '',
            'fallbackfile' => '',
            'autorebuildlist' => '0',
            'autostart' => (bool) $pc['autostart'],
            'timezone' => 'auto',
            'allowproxy' => '0',
            'charset' => 'ISO-8859-1',
            'servertype' => $pc['servertype'],
            'sourcetype' => $pc['sourcetype'],
            'template' => $pc['template'],
        ];

        // call api if not import action
        if (!isset($oc['import']) || !$oc['import']) {
            $this->_apiSystemCall($server, 'provision', $params);
        }

        $model->server_id = $pc['server_id'];
        $model->username = $username;
        $model->pass = $this->encryptPass($password);
        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        return array_merge($params, ['server' => $server]);
    }

    private function _genUsername($client)
    {
        $u = preg_replace('/([^@]*).*/', '$1', $client['email']);
        $u = str_replace('.', '', $u);
        $sql = 'SELECT id FROM service_centovacast WHERE username = :u';
        $exists = $this->di['db']->getCell($sql, ['u' => $u]);
        if ($exists) {
            $u = $u.rand(1, 100);
        }

        return $u;
    }

    /**
     * Suspend order.
     *
     * @param $order
     *
     * @return bool
     */
    public function suspend($order, $model)
    {
        if (!is_object($model)) {
            throw new \Box_Exception('Could not suspend order. Service was not created', null, 7456);
        }
        $server = $this->getServer($model->server_id);
        $params = [
            'username' => $model->username,
            'status' => 'disabled',
        ];
        $this->_apiSystemCall($server, 'setstatus', $params);

        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        return true;
    }

    /**
     * @param $order
     *
     * @return bool
     */
    public function unsuspend($order, $model)
    {
        if (!is_object($model)) {
            throw new \Box_Exception('Could not unsuspend order. Service was not created', null, 7456);
        }

        $server = $this->getServer($model->server_id);
        $params = [
            'username' => $model->username,
            'status' => 'enabled',
        ];
        $this->_apiSystemCall($server, 'setstatus', $params);

        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        return true;
    }

    /**
     * @param $order
     *
     * @return bool
     */
    public function cancel($order, $model)
    {
        return $this->suspend($order, $model);
    }

    /**
     * @param $order
     *
     * @return bool
     */
    public function uncancel($order, $model)
    {
        return $this->unsuspend($order, $model);
    }

    /**
     * @param $order
     *
     * @return bool
     */
    public function delete($order, $model)
    {
        if (is_object($model)) {
            try {
                $server = $this->getServer($model->server_id);
                $params = [
                    'username' => $model->username,
                ];
                $this->_apiSystemCall($server, 'terminate', $params);
            } catch (\Exception $e) {
                error_log($e->getMessage());
            }

            $this->di['db']->trash($model);
        }

        return true;
    }

    public function start($model)
    {
        $this->_apiServerCall($model, 'start');

        return true;
    }

    public function stop($model)
    {
        $this->_apiServerCall($model, 'stop');

        return true;
    }

    public function restart($model)
    {
        $this->_apiServerCall($model, 'restart');

        return true;
    }

    public function reload($model)
    {
        $this->_apiServerCall($model, 'reload');

        return true;
    }

    public function getaccount($model)
    {
        $res = $this->_apiServerCall($model, 'getaccount');

        return $res['account'];
    }

    public function getstatus($model)
    {
        $res = $this->_apiServerCall($model, 'getstatus');

        return $res['status'];
    }

    public function getsongs($model)
    {
        $res = $this->_apiServerCall($model, 'getsongs');

        return $res['songs']['row'] ?? [];
    }

    public function reconfigure($model, $params)
    {
        return $this->_apiServerCall($model, 'reconfigure', $params, true);
    }

    public function info($model)
    {
        $server = $this->getServer($model->server_id);
        $params = [
            'username' => $model->username,
        ];
        $res = $this->_apiSystemCall($server, 'info', $params);

        return $res['row'];
    }

    public function version($server)
    {
        return $this->_apiSystemCall($server, 'version');
    }

    public function apiConnection($server)
    {
        $params = [
            'username' => 'all',
        ];

        return $this->_apiSystemCall($server, 'info', $params);
    }

    public function cpanelUrl($model)
    {
        $server = $this->getServer($model->server_id);

        return $server['url'];
    }

    /**
     * @param string $method
     */
    private function _apiServerCall($model, $method, $arguments = [], $admin = false)
    {
        $account_username = $model->username;
        $account_password = $this->di['crypt']->decrypt($model->pass, $this->_salt);
        $server = $this->getServer($model->server_id);

        $centovacast_url = $server['url'];

        if ($admin) {
            $account_password = 'admin|'.$server['secret'];
        }

        require_once dirname(__FILE__).'/ccapiclient/ccapiclient.php';
        $server = new CCServerAPIClient($centovacast_url);
        $server->cc_initialize($centovacast_url);
        $server->call($method, $account_username, $account_password, $arguments);

        if (!$server->success) {
            throw new \Exception($server->error);
        }

        return $server->bb_data;
    }

    /**
     * @param string $method
     */
    private function _apiSystemCall($server, $method, $params = [])
    {
        require_once dirname(__FILE__).'/ccapiclient.php';
        $centovacast_url = $server['url'];
        $admin_password = $server['secret'];
        $system = new CCSystemAPIClient($centovacast_url);
        $system->cc_initialize($centovacast_url);
        $system->call($method, $admin_password, $params);

        if (!$system->success) {
            throw new \Exception($system->error);
        }

        return $system->bb_data;
    }

    public function encryptPass($password)
    {
        return $this->di['crypt']->encrypt($password, $this->_salt);
    }
}
