<?php

require_once(BASE.'vendor/autoload.php');

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

class BaseModel {

    protected $connection;
    protected $config;

    public function __construct()
    {
        if (!$this->connection)
        {
            $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
                             PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
            $database = $this->get('database');
            try
            {
                $this->connection = new PDO(
                    'mysql:host=' . $database['server'] . ';dbname=' .
                    $database['name'] . ';port=3306' . ';charset=utf8',
                    $database['user'], $database['password'], $options
                );
            }
            catch(PDOException $Exception)
            {
                return false;
            }
        }
        return $this->connection;
    }

    public function get($key)
    {
        if (!$this->config)
        {
            $config_file = BASE.'config.php';

            if (!file_exists($config_file))
            {
                return false;
            }

            $this->config = require $config_file;
        }

        return $this->config[$key];
    }

    protected function getOwnPlatformName()
    {
        return stripslashes($this->getOwnPlatformDetail("MarketName"));
    }

    protected function getOwnUUID()
    {
        return $this->getOwnPlatformDetail("MarketUUID");
    }

    protected function getOwnPlatformURL()
    {
        return $this->getOwnPlatformDetail("MarketURL");
    }

    protected function getOwnPlatformLocation()
    {
        return $this->getOwnPlatformDetail("MarketAddressZipcode").' '
              .$this->getOwnPlatformDetail("MarketAddressCity");
    }

    protected function getOwnPlatformDetail($key_name)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("
            SELECT value_text FROM ".$prefix."settings WHERE name = :key_name LIMIT 1");
        $query->bindParam(":key_name", $key_name, PDO::PARAM_STR);
        $query->execute();
        $platform = $query->fetch();
        return $platform->value_text;
    }

    public function getPlatformIDByUUID($uuid)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("
            SELECT id FROM `".$prefix."platforms` WHERE uuid = :uuid LIMIT 1");
        $query->bindParam(":uuid", $uuid, PDO::PARAM_STR);
        if ($query->execute() && $query->rowCount() > 0)
        {
            $platform = $query->fetch();
            return $platform->id;
        }
        else
        {
            return false;
        }
    }

    protected function getPlatformsPublicKey($uuid)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("
            SELECT remote_public_key FROM `".$prefix."platforms` WHERE uuid = :uuid LIMIT 1");
        $query->bindParam(":uuid", $uuid, PDO::PARAM_STR);
        $query->execute();
        $platform = $query->fetch();
        return $platform->remote_public_key;
    }

    private function getPlatformsPrivateKey($uuid)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("
            SELECT private_key FROM `".$prefix."platforms` WHERE uuid = :uuid LIMIT 1");
        $query->bindParam(":uuid", $uuid, PDO::PARAM_STR);
        $query->execute();
        $platform = $query->fetch();
        return $platform->private_key;
    }

    public function verifySignature($data,$signature,$platform_uuid)
    {
        $public_key = $this->getPlatformsPublicKey($platform_uuid);
        if ($public_key === false) {
            $result['verified'] = false;
            $result['reason'] = 'No Platform with that UUID in Database';
            return $result;
        }
        $rsa = new RSA();
        $rsa->setSignatureMode(RSA::SIGNATURE_PSS);
        $rsa->setHash("sha1");
        $rsa->setMGFHash("sha256");
        $rsa->setSaltLength(0);
        $rsa->loadKey($public_key);
        $signature = base64_decode($signature);
        $result['signature'] = $signature;
        try {
            $result['verified'] = $rsa->verify($data, $signature);
        } catch (Exception $e) {
            $result['verified'] = $e;
        }
        $rsa = null;
        return $result;
    }

    public function decryptData($data,$platform_uuid)
    {
        $encrypted_data = base64_decode($data);
        $private_key = $this->getPlatformsPrivateKey($platform_uuid);
        $rsa = new RSA();
        $rsa->loadKey($private_key);
        $rsa->setEncryptionMode(RSA::ENCRYPTION_OAEP);
        $rsa->setHash('sha1');
        $rsa->setMGFHash('sha256');
        $data_json = $rsa->decrypt($encrypted_data);
        $rsa = null;
        return json_decode($data_json);
    }

    protected function generateKeypair()
    {
        $rsa = new RSA();
        extract($rsa->createKey());
        $keys = array();
        $keys['private_key'] = $privatekey;
        $keys['public_key']  = $publickey;
        $rsa = null;
        return $keys;
    }

    public function verifyData($encrypted_data)
    {
        $signature          = $_SERVER['HTTP_X_SIGNATURE'];
        $origin_uuid        = $_SERVER['HTTP_X_ORIGIN'];
        $timestamp_header   = (int) $_SERVER['HTTP_X_TIMESTAMP'];

        $response = array();
        $response['status'] = 'insecure';

        $verified = array();
        $verified['verified'] = false;

        $verified = $this->verifySignature($encrypted_data,$signature,$origin_uuid);

        if (!$verified['verified'])
        {
            $response["error"]      = "invalid signature: ".$signature.' verified: '.json_encode($verified);
            return $response;
        }

        $decrypted_data = $this->decryptData($encrypted_data,$origin_uuid);

        if (!$decrypted_data)
        {
            $response["error"]      = "decryption failed";
            return $response;
        }

        if ($decrypted_data->own_uuid != $origin_uuid)
        {
            $response["error"]      = "uuid was tampered with";
            return $response;
        }

        if ($decrypted_data->timestamp != $timestamp_header)
        {
            $response["error"]      = "timestamp was tampered with";
            return $response;
        }

        if ((time() - $decrypted_data->timestamp) > 180)
        {
            $response["error"]      = "possible replay attack: timestamp older than 3 minutes";
            return $response;
        }
        $response['status']         = 'secure';
        $response['secure_data']    = $decrypted_data;
        return $response;
    }

    public function logApiAccess($data)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("INSERT INTO ".$prefix."api_log
                                                (success,
                                                 external_uuid,
                                                 endpoint,
                                                 data,
                                                 timestamp)
                                             VALUES
                                                (:success,
                                                 :external_uuid,
                                                 :endpoint,
                                                 :data,
                                                 NOW())");
        $query->bindParam(":success",       $data->success,       PDO::PARAM_STR);
        $query->bindParam(":external_uuid", $data->external_uuid, PDO::PARAM_STR);
        $query->bindParam(":endpoint",      $data->endpoint,      PDO::PARAM_STR);
        $query->bindParam(":data",          $data->data,          PDO::PARAM_STR);
        if ($query->execute())
        {
            $status = "success";
        }
        else
        {
            $status = "insert log error: ";
        }
        return $status;
    }

    public function insertEmailToSend($email)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("INSERT INTO ".$prefix."mails
                                                (email,
                                                 subject,
                                                 body)
                                             VALUES
                                                (:email,
                                                 :subject,
                                                 :body)");
        $query->bindParam(":email",     $email->email,      PDO::PARAM_STR);
        $query->bindParam(":subject",   $email->subject,    PDO::PARAM_STR);
        $query->bindParam(":body",      $email->body,       PDO::PARAM_STR);
        if ($query->execute())
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    protected function updateSuccessfulSyncStatus($platformID)
    {
        $prefix = $this->get('db_prefix');
        $query = $this->connection->prepare("UPDATE ".$prefix."platforms
                                             SET last_successful_sync = NOW()
                                             WHERE id = :platform_id");
        $query->bindParam(":platform_id", $platformID,      PDO::PARAM_INT);
        if ($query->execute() && $query->rowCount() > 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
