<?php

namespace CMDB;

use CMDB\Entities\CmdbItemEntity;
use CMDB\Models\CmdbItemModel;
use FR\V1_0\Entities\RemoteHub\RemoteHub;
use FR\V1_0\Models\SetupHubModel;

class CMDBResource extends \CfProtectedResource
{
    public function __construct($parameters)
    {
        parent::__construct($parameters);
        $setupHubModel = new SetupHubModel(\CfSettings::getInstance()->getConnection(), $this->username);
        if ($setupHubModel->getHubRole() == RemoteHub::SUPERHUB_ROLE && $setupHubModel->isConfigured()) {
            throw new \Exception('CMDB API is not allowed on the superhub.');
        }
    }
}

/**
 * @uri /cmdb 0
 */
class CMDB extends CMDBResource
{
    /**
     * @var CmdbItemModel
     */
    private $cmdbItemModel;

    public function __construct($parameters)
    {
        parent::__construct($parameters);
        $this->cmdbItemModel = new CmdbItemModel($this->username);
    }


    /**
     * @rbacName Get configs list
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.list
     * @rbacAllowedByDefault
     */
    public function get($request)
    {
        $response = new \Response($request);
        $response->code = \Response::OK;

        try {
            $data = $this->cmdbItemModel->list(\Utils::processQueryApiParams($_REQUEST));
            $response->body = json_encode($data);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }

    /**
     * @rbacName Batch create
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.batch_create
     */
    public function post($request)
    {
        $requestData = \Utils::getValidJsonData($request->data);
        $response = new \Response($request);
        $response->code = \Response::CREATED;

        try {
            $cmdbItem = CmdbItemEntity::fromRequest($requestData);
            cfapi_host_get($this->username, $cmdbItem->getHostKey()); // check if user has RBAC access to this host
            $this->cmdbItemModel->createOrUpdate($cmdbItem);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }
}


/**
 * @uri /cmdb/:hostkey 0
 */
class CMDBItem extends CMDBResource
{
    /**
     * @var CmdbItemModel
     */
    private $cmdbItemModel;

    public function __construct($parameters)
    {
        parent::__construct($parameters);
        $this->cmdbItemModel = new CmdbItemModel($this->username);
    }

    /**
     * @rbacName Get config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.get
     * @rbacAllowedByDefault
     */
    public function get($request, $hostkey)
    {
        $response = new \Response($request);
        $response->code = \Response::OK;
        $value = json_encode($this->cmdbItemModel->get($hostkey));
        if ($value === null) {
            $response->code = \Response::NOTFOUND;
        }
        $response->body = $value;

        return $response;
    }

    /**
     * @rbacName Create config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.create
     */
    public function patch($request, string $hostkey)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $requestData = \Utils::getValidJsonData($request->data);
        $requestData->hostkey = $hostkey;
        $response = new \Response($request);
        $response->code = \Response::OK;

        try {
            $newCmdbItem = CmdbItemEntity::fromRequest($requestData);
            $cmdbFromDB = $this->cmdbItemModel->get($hostkey);

            if ($cmdbFromDB !== false && is_array($cmdbFromDB)) {
                $combinedCMDB = CmdbItemEntity::merge($newCmdbItem, CmdbItemEntity::fromDB($cmdbFromDB));
                $this->cmdbItemModel->update($combinedCMDB);
            } else {
                $this->cmdbItemModel->createOrUpdate($newCmdbItem);
            }
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }

    /**
     * @param $request
     * @param $hostkey
     * @return \Response
     *
     * @rbacName Delete config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.delete
     */
    public function delete($request, string $hostkey)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $response = new \Response($request);
        $response->body = $this->cmdbItemModel->delete($hostkey);
        $response->code = \Response::NOCONTENT;
        return $response;
    }

}

/**
 * @uri /cmdb/:hostkey/:type/:name 0
 */
class CMDBSpecificItem extends CMDBResource
{
    /**
     * @var CmdbItemModel
     */
    private $cmdbItemModel;

    public function __construct($parameters)
    {
        parent::__construct($parameters);
        $this->cmdbItemModel = new CmdbItemModel($this->username);
    }

    /**
     * @rbacName Get config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.get
     * @rbacAllowedByDefault
     */
    public function get($request, string $hostkey, $type, $name)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $response = new \Response($request);
        $response->code = \Response::OK;

        try {
            $data = $this->cmdbItemModel->getByName($hostkey, $type, $name);
            $response->body = json_encode($data);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        if ($data === null) {
            $response->body = "$type data with name $name is not found";
            $response->code = \Response::NOTFOUND;
        }

        return $response;
    }

    /**
     * @rbacName Create config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.create
     */
    public function post($request, string $hostkey, $type, $name)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $requestData = json_decode($request->data, $associative = true);
        CMDBSpecificItem::validateCMDBRequest($name, $requestData);
        $response = new \Response($request);
        $response->code = \Response::OK;

        try {
            $this->cmdbItemModel->insertOrUpdateByName($hostkey, $type, $name, $requestData);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }

    /**
     * @rbacName Update config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.update
     */
    public function patch($request, string $hostkey, $type, $name)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $requestData = json_decode($request->data, $associative = true);
        CMDBSpecificItem::validateCMDBRequest($name, $requestData);
        $response = new \Response($request);
        $response->code = \Response::OK;
        $existingConfig = $this->cmdbItemModel->getByName($hostkey, $type, $name);

        if ($existingConfig === null) {
            $response->body = "$type data with name $name is not found";
            $response->code = \Response::NOTFOUND;
            return $response;
        }

        try {
            $this->cmdbItemModel->updateByName($hostkey, $type, $name, $requestData, $existingConfig);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }

    /**
     * @rbacName Delete config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.delete
     */
    public function delete($request, string $hostkey, $type, $name)
    {
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host
        $response = new \Response($request);
        $response->code = \Response::NOCONTENT;

        try {
            $this->cmdbItemModel->deleteByName($hostkey, $type, $name);
        } catch (\InvalidArgumentException $exception) {
            $response->body = $exception->getMessage();
            $response->code = \Response::BADREQUEST;
        }

        return $response;
    }

    public static function validateCMDBRequest($name, $requestData)
    {
        foreach (['value', 'tags', 'comment'] as $key) {
            if (array_key_exists($key, $requestData)) {
                \Validator::maxCMDBValueLength($requestData[$key], ucfirst($key));
            }
        }
        \Validator::maxCMDBNameLength($name);
    }
}

/**
 * @uri /cmdb/:hostkey/json 0
 */
class CMDBViewConfig extends CMDBResource
{
    /**
     * @var CmdbItemModel
     */
    private $cmdbItemModel;

    public function __construct($parameters)
    {
        parent::__construct($parameters);
        $this->cmdbItemModel = new CmdbItemModel($this->username);
    }

    /**
     * @rbacName View host specific rendered JSON config
     * @rbacDescription
     * @rbacGroup CMDB
     * @rbacAlias cmdb.view-host-json
     * @rbacAllowedByDefault
     */
    public function get($request, string $hostkey)
    {
        $response = new \Response($request);
        $response->code = \Response::OK;
        cfapi_host_get($this->username, $hostkey); // check if user has RBAC access to this host

        $filePath = realpath(CMDB_PATH . "/$hostkey/host_specific.json");

        // Check if the resulted file path is within the cmdb directory
        if (strpos($filePath, CMDB_PATH) !== 0) {
            $response->code = \Response::NOTFOUND;
            $response->body = 'File not found or not readable.';
            return $response;
        }

        if (!file_exists($filePath)) {
            $response->code = \Response::NOTFOUND;
            $response->body = 'File not found or not readable.';
        } else {
            $response->body = file_get_contents($filePath);
        }

        return $response;
    }
}
