<?php

use phpseclib\Crypt\RSA;
use phpseclib\Math\BigInteger;

/**
 * ConnectionsModel
 *
 */
class ConnectionsModel
{
    public static function listConnectedPlatforms()
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT id,
                       name,
                       url,
                       location,
                       status,
                       uuid,
                       DATE_FORMAT(created_on, '%d.%m.%Y') AS created,
                       DATE_FORMAT(connected_since, '%d.%m.%Y') AS connected
                FROM ".$prefix."platforms
                WHERE status < 4
                ORDER BY status DESC, name ASC
                LIMIT 100";
        $query = $database->prepare($sql);
        $query->execute();
        $connections = array(); $i = 0;
        foreach($query->fetchAll() as $connection)
        {
            $connections[$i]            = new stdClass();
            $connections[$i]->id        = $connection->id;
            $connections[$i]->name      = $connection->name;
            $connections[$i]->url       = $connection->url;
            $connections[$i]->location  = $connection->location;
            $connections[$i]->send      = self::getCategoryConnections('send',
                                                                       $connection->id,
                                                                       'cat2',
                                                                       'string');
            $connections[$i]->receive   = self::getCategoryConnections('receive',
                                                                       $connection->id,
                                                                       'cat2',
                                                                       'string');
            $connections[$i]->status    = $connection->status;
            $connections[$i]->uuid      = $connection->uuid;
            $connections[$i]->created   = $connection->created;
            $connections[$i]->connected = $connection->connected;
            $i++;
        }
        return $connections;
    }

    public static function getOwnPlatformData()
    {
        $platformData['name']       = self::getOwnPlatformName();
        $platformData['url']        = self::getOwnPlatformURL();
        $platformData['location']   = self::getOwnPlatformLocation();
        $platformData['uuid']       = self::getOwnUuid();
        return $platformData;
    }

    public static function constructConnectionRequestData($public_key,$receive = true)
    {
        $data               = array();
        $data               = self::getOwnPlatformData();
        $data['public_key'] = $public_key;
        $preselect          = implode(',',Admin::adminGetCategoryKeys());
        $data['send']       = $preselect;
        $data['receive']    = $receive ? '' : $preselect;
        return $data;
    }

    public static function connect($url = false, $silent = false)
    {
        if (Request::post('url') || ($url != false))
        {
            if ($url != false)
            {
                $newURL = $url;
            } else {
                $newURL = Request::post('url');
            }
            $keys           = self::generateKeypair();
            $url            = self::constructApiURL($newURL);
            $data           = self::constructConnectionRequestData($keys['public_key'], $silent);
            $data_json      = json_encode($data);
            $response_json  = self::postToApi($url,$data_json,'',time());
            $response       = json_decode($response_json);

            if (isset($response->status) && $response->status == "success")
            {
                $platform                       = new stdClass();
                $platform->name                 = stripslashes($response->data->platform_name);
                $platform->url                  = $response->data->platform_url;
                $platform->location             = $response->data->location;
                $platform->uuid                 = $response->data->uuid;
                $platform->remote_public_key    = $response->data->public_key;
                $platform->own_public_key       = $keys['public_key'];
                $platform->private_key          = $keys['private_key'];

                $platform->send                 = $data['send'];
                $platform->receive              = $data['receive'];

                $status = self::insertRemotePlatform($platform);

                if ($status == 'success' && $silent == false)
                {
                    Session::add('feedback_positive',
                                 Text::get('FEEDBACK_PLATFORM_ADDED'));
                }
                elseif ($silent == false)
                {
                    Session::add('feedback_negative',
                                 Text::get('FEEDBACK_PLATFORM_ADD_ERROR').'<br>'.
                                 'Local Error: Platform added remotely but failed to save localy.
                                  Please contact remote Platform to manually delete connection attempt,
                                  then try again.');
                }
            }
            else
            {
                Session::add('feedback_negative',
                             Text::get('FEEDBACK_PLATFORM_ADD_ERROR').'<br>'.
                             'Remote Error: '.$response->error->message);
            }
        }
    }

    public static function confirm($uuid)
    {
        $platform = new stdClass();
        $data = new stdClass();
        $platform->remote_uuid  = $uuid;
        $data->own_uuid         = self::getOwnUUID();
        $platform->url          = self::getRemoteURLByUUID($platform->remote_uuid)
                                        ."api/platforms/confirm/".$data->own_uuid;
        $data->timestamp        = time();
        $data_json              = json_encode($data);

        $encrypted_data         = self::encryptData($data_json,
                                                    $platform->remote_uuid);
        $signature              = self::signData($encrypted_data,
                                                 $platform->remote_uuid);
        $response_json          = self::postToApi($platform->url,
                                                  $encrypted_data,
                                                  $signature,
                                                  $data->timestamp);
        $response = json_decode($response_json);

        if (isset($response->status) &&
            $response->status == 'success' &&
            isset($response->data->uuid) &&
            $response->data->uuid == $data->own_uuid)
        {
            $platform->id = self::getPlatformIDByUUID($platform->remote_uuid);
            if (self::setPlatformStatus($platform->id,3,true))
            {
                Session::add('feedback_positive',
                             Text::get('FEEDBACK_PLATFORM_CONNECTED'));
            }
        }
    }

    private static function insertRemotePlatform($platform)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $query = $database->prepare("INSERT INTO ".$prefix."platforms
                                        (name,
                                         url,
                                         location,
                                         status,
                                         uuid,
                                         remote_public_key,
                                         own_public_key,
                                         private_key,
                                         modified,
                                         created_on)
                                     VALUES
                                        (:name,
                                         :url,
                                         :location,
                                         1,
                                         :uuid,
                                         :remote_public_key,
                                         :own_public_key,
                                         :private_key,
                                         NOW(),
                                         NOW())");

        $query->bindParam(':name',              $platform->name,                PDO::PARAM_STR);
        $query->bindParam(':url',               $platform->url,                 PDO::PARAM_STR);
        $query->bindParam(':location',          $platform->location,            PDO::PARAM_STR);
        $query->bindParam(':uuid',              $platform->uuid,                PDO::PARAM_STR);
        $query->bindParam(':remote_public_key', $platform->remote_public_key,   PDO::PARAM_STR);
        $query->bindParam(':own_public_key',    $platform->own_public_key,      PDO::PARAM_STR);
        $query->bindParam(':private_key',       $platform->private_key,         PDO::PARAM_STR);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $platform_id = self::getPlatformIDByUUID($platform->uuid);
            self::insertCategoryConnections('send',$platform->send,$platform_id,'cat2');
            self::insertCategoryConnections('receive',$platform->receive,$platform_id,'cat2');
            $response = "success";
        }
        else
        {
            $response = "error: ".$query->error;
        }
        return $response;
    }

    private static function insertCategoryConnections($type,$categoryIDs,$platform_id,$cat = 'cat2')
    {
        if (!in_array($type,array('send','receive','remote_receive')))
        {
            return false;
        }
        if (!in_array($cat,array('cat1','cat2','cat3')))
        {
            return false;
        }

        self::deleteCategoryConnections($type,$platform_id);

        if ($categoryIDs != '')
        {
            $categoryIDsArray = explode(',',rtrim(trim($categoryIDs),','));

            $prefix = Config::get('DB_PREFIX');
            $database = DatabaseFactory::getFactory()->getConnection();
            $query = $database->prepare("INSERT INTO ".$prefix."category_platforms
                                            (".$cat.",
                                             ".$type.")
                                         VALUES
                                            (:cat_id,
                                             :platform_id)");
            $i = 0;
            foreach ($categoryIDsArray as $cat_id)
            {
                $query->bindParam(':cat_id',        $cat_id,        PDO::PARAM_INT);
                $query->bindParam(':platform_id',   $platform_id,   PDO::PARAM_INT);
                if ($query->execute() && $query->rowCount() > 0)
                {
                    $i++;
                }
            }
            if ($i == count($categoryIDsArray))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return true;
        }
    }

    public static function getCategoryConnections($type,$platform_id,$cat,$format = 'array')
    {
        if (!in_array($type,array('send','receive','remote_receive')))
        {
            return false;
        }
        if (!in_array($cat,array('cat1','cat2','cat3')))
        {
            return false;
        }
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $query = $database->prepare("SELECT ".$cat."
                                     FROM ".$prefix."category_platforms
                                     WHERE ".$type." = :platform_id");
        $query->bindParam(':platform_id',   $platform_id,   PDO::PARAM_INT);
        $query->execute();

        $categories_array = array();
        foreach ($query->fetchAll() as $category)
        {
            $categories_array[] = $category->{$cat};
        }

        if ($format == 'string')
        {
            return implode(',',$categories_array);
        }
        else
        {
            return $categories_array;
        }
    }

    private static function deleteCategoryConnections($type,$platform_id)
    {
        if (!in_array($type,array('send','receive','remote_receive')))
        {
            return false;
        }
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $query = $database->prepare("DELETE FROM ".$prefix."category_platforms
                                     WHERE ".$type." = :platform_id");
        $query->bindParam(':platform_id',   $platform_id,   PDO::PARAM_INT);
        if ($query->execute())
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public static function edit($platform_id)
    {
        $status_update = false;
        $send_insert = false;
        $receive_insert = false;

        if (Request::post('connection_status_'.$platform_id))
        {
            $new_status = (int) Request::post('connection_status_'.$platform_id);
            if (in_array($new_status,array(2,3)))
            {
                $status_update = self::setPlatformStatus($platform_id,$new_status);
            }
        }

        $send = '';
        if (Request::post('send_'.$platform_id) != false)
        {
            foreach (Request::post('send_'.$platform_id) as $send_this)
            {
                $send .= $send_this.',';
            }
        }

        $receive = '';
        if (Request::post('receive_'.$platform_id) != false)
        {
            foreach (Request::post('receive_'.$platform_id) as $receive_this)
            {
                $receive .= $receive_this.',';
            }
        }

        if (self::insertCategoryConnections('send',$send,$platform_id,'cat2'))
        {
            $send_insert = true;
        }

        if (self::insertCategoryConnections('receive',$receive,$platform_id,'cat2'))
        {
            $receive_insert = true;
        }

        if ($status_update === true || $send_insert === true || $receive_insert === true)
        {
            AdminModel::updatePlatformModified($platform_id);
            Session::add('feedback_positive',
                         Text::get('FEEDBACK_PLATFORM_PREFERENCES_SAVED'));
        }
    }

    public static function delete($platform_id)
    {
        // set status to 4 (deleted) to hide it from the list and start the cleanup logic
        self::setPlatformStatus($platform_id,4);
        $deleteRemoteOffers = self::deleteAllOffersOfRemotePlatform($platform_id);
        $deleteCategoryLink1 = self::deleteCategoryConnections('send',$platform_id);
        $deleteCategoryLink2 = self::deleteCategoryConnections('receive',$platform_id);
        $deleteCategoryLink3 = self::deleteCategoryConnections('remote_receive',$platform_id);
        if ($deleteRemoteOffers && $deleteCategoryLink1 && $deleteCategoryLink2 && $deleteCategoryLink3)
        {
            Session::add('feedback_positive',
                         Text::get('FEEDBACK_PLATFORM_DELETED'));
        } else {
            Session::add('feedback_negative',
                         Text::get('FEEDBACK_PLATFORM_DELETE_FAILURE'));
        }
    }

    private static function deleteAllOffersOfRemotePlatform($platform_id)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $query = $database->prepare("DELETE FROM ".$prefix."offers
                                     WHERE external_platform_id = :id
                                       AND external = 1");

        $query->bindParam(':id',        $platform_id,   PDO::PARAM_INT);

        if ($query->execute())
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    private static function setPlatformStatus($platform_id,$status,$setSince = false)
    {
        if ($setSince === true)
        {
            $connectedSince = ', connected_since = NOW()';
        }
        else
        {
            $connectedSince = '';
        }

        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $query = $database->prepare("UPDATE ".$prefix."platforms
                                     SET status = :status".$connectedSince."
                                     WHERE id = :id
                                     LIMIT 1");

        $query->bindParam(':status',    $status,        PDO::PARAM_INT);
        $query->bindParam(':id',        $platform_id,   PDO::PARAM_INT);

        if ($query->execute() && $query->rowCount() > 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    protected static function getOwnPlatformName()
    {
        return Config::getFromDB('MarketName');
    }

    public static function getOwnPlatformURL()
    {
        return Config::getFromDB('MarketURL');
    }

    protected static function getOwnPlatformLocation()
    {
        return Config::getFromDB('MarketAddressZipcode').' '
              .Config::getFromDB('MarketAddressCity');
    }

    public static function getOwnUUID()
    {
        return Config::getFromDB('MarketUUID');
    }

    public static function getRemoteURLByUUID($uuid)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT url
                FROM ".$prefix."platforms
                WHERE uuid = :uuid
                LIMIT 1";

        $query = $database->prepare($sql);
        $query->bindParam(':uuid', $uuid, PDO::PARAM_STR);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $result = $query->fetch();
            return $result->url;
        }
        else
        {
            return false;
        }
    }

    public static function getPlatformIDByUUID($uuid)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT id
                FROM ".$prefix."platforms
                WHERE uuid = :uuid
                LIMIT 1";

        $query = $database->prepare($sql);
        $query->bindParam(':uuid', $uuid, PDO::PARAM_STR);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $result = $query->fetch();
            return $result->id;
        }
        else
        {
            return false;
        }
    }

    public static function getPlatformDetailsByID($id)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT name,
                       url,
                       location,
                       status,
                       uuid,
                       DATE_FORMAT(connected_since, '%d.%m.%Y') AS connected_since
                FROM ".$prefix."platforms
                WHERE id = :id
                LIMIT 1";

        $query = $database->prepare($sql);
        $query->bindParam(':id', $id, PDO::PARAM_INT);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $result = $query->fetch();
            return $result;
        }
        else
        {
            return false;
        }
    }

    private static function getPublicKeyForUUID($uuid)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT remote_public_key
                FROM ".$prefix."platforms
                WHERE uuid = :uuid
                LIMIT 1";

        $query = $database->prepare($sql);
        $query->bindParam(':uuid',      $uuid,      PDO::PARAM_STR);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $result = $query->fetch();
            return $result->remote_public_key;
        }
        else
        {
            return false;
        }
    }

    private static function getPrivateKeyForUUID($uuid)
    {
        $prefix = Config::get('DB_PREFIX');
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT private_key
                FROM ".$prefix."platforms
                WHERE uuid = :uuid
                LIMIT 1";

        $query = $database->prepare($sql);
        $query->bindParam(':uuid',      $uuid,      PDO::PARAM_STR);

        if ($query->execute() && $query->rowCount() > 0)
        {
            $result = $query->fetch();
            return $result->private_key;
        }
        else
        {
            return false;
        }
    }

    protected static function generateKeypair()
    {
        $rsa = new RSA();
        extract($rsa->createKey());

        $keys = array();
        $keys['private_key'] = $privatekey;
        $keys['public_key'] = $publickey;

        $rsa = null;
        return $keys;
    }

    public static function encryptData($data,$platform_uuid)
    {
        $remote_public_key = self::getPublicKeyForUUID($platform_uuid);

        $rsa = new RSA();
        $rsa->loadKey($remote_public_key);
        $rsa->setEncryptionMode(RSA::ENCRYPTION_OAEP);
        $rsa->setHash('sha1');
        $rsa->setMGFHash('sha256');
        $ciphertext = $rsa->encrypt($data);

        $rsa = null;
        return base64_encode($ciphertext);
    }

    public static function signData($data,$platform_uuid)
    {
        $private_key = self::getPrivateKeyForUUID($platform_uuid);

        $rsa = new RSA();
        $rsa->setSignatureMode(RSA::SIGNATURE_PSS);
        $rsa->setHash('sha1');
        $rsa->setMGFHash('sha256');
        $rsa->setSaltLength(0);
        $rsa->loadKey($private_key);
        $signature = $rsa->sign($data);

        $rsa = null;
        return base64_encode($signature);
    }

    public static function constructApiURL($url)
    {
        // todo: check for valid url
        $url = rtrim($url, '/');
        return $url."/api/platforms";
    }

    public static function postToApi($url,$data,$signature,$timestamp,$type = 'POST')
    {
        $ch = curl_init();

        $curl_headers = array();
        $curl_headers[] = 'X-SIGNATURE: '.$signature;
        $curl_headers[] = 'X-ORIGIN: '.self::getOwnUUID();
        $curl_headers[] = 'X-TIMESTAMP: '.$timestamp;
        $curl_headers[] = 'X-API-VERSION: 1.0';
        if ($type != 'POST')
        {
            $curl_headers[] = 'X-HTTP-METHOD: '.$type;
        }

        // set URL and other appropriate options
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_VERBOSE, 1);

        // grab URL and pass it to the browser
        $response = curl_exec($ch);

        // close cURL resource, and free up system resources
        curl_close($ch);
        return $response;
    }
}
