<?php
namespace Eater\Glim\Service;


use Eater\Glim\Model\Server as ServerModel;
use GuzzleHttp\Client as HttpClient;
use Eater\Glim\Core;
use Psr\Http\Message\ResponseInterface;

class Server extends Main
{
    const MANAGEMENT_PORT = 7864;

    private $httpClient = null;

    private function getHttpClient() {
        if ($this->httpClient === null) {
            $this->httpClient = new HttpClient();
        }

        return $this->httpClient;
    }

    public function getCsrFromServer(ServerModel $server) {
        $response = $this->doSignedRequest($server, '/create-csr', [
            'hostname' => $server->getFqdn()
        ]);

        if ($response->getStatusCode() !== 200) {
            throw new \Exception('Requesting CSR failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
        }

        $data = $this->decryptResponse($server, $response);

        return $data['csr'];
    }

    /**
     * @param ServerModel $server
     * @throws \Exception
     */
    public function deliverSignedCertificate(ServerModel $server) {
        $response = $this->doSignedRequest($server, '/deliver-crt', [
            'certificate' => $server->getCertificate()
        ]);

        if ($response->getStatusCode() !== 200) {
            throw new \Exception('Delivering signed certificate failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
        }

        $server->setStatus('signed');
        $server->save();
    }


    public function deliverOpenVPNConfig(ServerModel $server) {
        /** @var \Twig_Environment $twig */
        $twig = $this->get('twig');
        $config = $twig->render('etc/openvpn-server.conf.twig', [
            'server' => $server
        ]);

        $response = $this->doSignedRequest($server, '/update-openvpn-config', [
            'config' => $config
        ]);

        if ($response->getStatusCode() !== 200) {
            throw new \Exception('Updating openvpn server config failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
        }
    }

    /**
     * @param ServerModel $server
     * @param string $path
     * @param string $data
     * @return \Psr\Http\Message\ResponseInterface
     * @throws \Exception
     */
    public function doSignedRequest(ServerModel $server, $path, $data) {
        $client = $this->getHttpClient();

        /** @var CA $ca */
        $ca = $this->get('ca');

        $data['signature'] = bin2hex($ca->signWithCA($server->getFingerprint()));

        $json = json_encode($data);

        $password = bin2hex(openssl_random_pseudo_bytes(48));
        $pubKey = openssl_get_publickey($server->getPublicKey());
        $success = openssl_public_encrypt($password, $crypted, $pubKey, OPENSSL_PKCS1_PADDING);

        if (!$success) {
            throw new \Exception('Encrypting data failed: ' . openssl_error_string() . openssl_error_string());
        }
        
        $body = bin2hex($crypted) . bin2hex(openssl_encrypt($json, 'blowfish', substr($password, 0, 32), true, substr($password, 32, 8)));
        return $client->post('http://' . $server->getExternalIp() . ':' . static::MANAGEMENT_PORT . $path, [
            'body' => $body
        ]);
    }

    public function decryptResponse(ServerModel $server, ResponseInterface $response) {
        $body = $response->getBody()->getContents();
        $this->get('logger')->addDebug($body);
        $encryptedPasswordAndIV = hex2bin(substr($body, 0, 512));
        $encryptedBody = hex2bin(substr($body, 512));

        /** @var CA $ca */
        $ca = $this->get('ca');

        $success = openssl_private_decrypt($encryptedPasswordAndIV, $passwordAndIV, $ca->getPrivateKey(), OPENSSL_PKCS1_PADDING);
        if (!$success) {
            throw new \Exception('Decrypting data failed: ' . openssl_error_string() . openssl_error_string());
        }

        $json = openssl_decrypt($encryptedBody, 'blowfish', substr($passwordAndIV, 0, 32), true, substr($passwordAndIV, 32, 8));
        $data = \json_decode($json, true);
        $ver = openssl_verify($server->getFingerprint(), hex2bin($data['signature']), $server->getPublicKey(), OPENSSL_ALGO_SHA256);

        if ($ver === 1) {
            return $data;
        } else {
            throw new \Exception("Checking signature failed ($ver): " . openssl_error_string());
        }
    }
}
