<?php
namespace SmartDataContext\Controller;

use SmartDataContext\Persistence\SmartDataContextCrud;
use SmartDataContext\Persistence\DBManager;
use SmartDataContext\Domain\SmartDataContext;

use Slim\Psr7\Request;
use Slim\Psr7\Response;

use OpenApi\Attributes as OA;

class Crud {

    private function _basicValidation($params, $request, $response) {
        # Basic validation
        $body = $request->getParsedBody();
        $errors = [];
        if (! $body) {
            $errors[] = "missing body";
        }
        $domain = $this->_get_value_or_default($params, 'domain', null);
        if (! $domain) {
            $errors[] = "missing domain";
        }
        return [$domain, $body, $errors];
    }

    function create(Request $request, Response $response, array $params) {
        # Basic validation
        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        # Parameters
        $content = $this->_get_value_or_default($body, 'content', []);
        $features = $this->_get_value_or_default($body, 'features', []);
        $t0 = $this->_get_value_or_default($body, 't0', -1);
        $t1 = $this->_get_value_or_default($body, 't1', -1);

        $smartDataSources = $this->_get_value_or_default($body, 'smartDataSources', []);
        $smartDataUnits = $this->_get_value_or_default($body, 'smartDataUnits', []);

        # Validate parameters
        $errors = array_merge(
            SmartDataContext::ValidateContent($content),
            SmartDataContext::ValidateFeatures($features),
            SmartDataContext::ValidateTimestamp($t0,$t1),
            SmartDataContext::ValidateContent($content),
            SmartDataContext::ValidateFeatures($features),
            SmartDataContext::ValidateTimestamp($t0,$t1),
            SmartDataContext::ValidateSmartDataSources($smartDataSources),
            SmartDataContext::ValidateSmartDataUnits($smartDataUnits));

        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        # Create new SmartDataContext
        $persistence = new SmartDataContextCrud($this->_get_db());
        $result = $persistence->create(domain: $domain, t0: $t0, t1: $t1, content: $content, features: $features, smartDataSources: $smartDataSources,
            smartDataUnits: $smartDataUnits);

        if ($result['success']) {
            return $this->return_json($response, $result["contents"]);
        } else {
            return $this->return_error($response, $result["errors"], 400);
        }
    }

    function associate(Request $request, Response $response, array $params) {
        # Basic validation
        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', null);
        if (! isset($smartdatacontextids)) {
            return $this->return_error($response, ['Missing smartDataContextIds'], 400);
        }
        if (! is_array($smartdatacontextids)) {
            $smartdatacontextids = [$smartdatacontextids];
        }
        $smartDataSources = $this->_get_value_or_default($body, 'smartDataSources', []);
        $smartDataUnits = $this->_get_value_or_default($body, 'smartDataUnits', []);

        $errors = array_merge(
            SmartDataContext::ValidateSmartDataSources($smartDataSources),
            SmartDataContext::ValidateSmartDataUnits($smartDataUnits));

        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $persistence = new SmartDataContextCrud($this->_get_db());
        $fullResult = [];
        foreach ($smartdatacontextids as $id) {
            $fullResult[] = $persistence->associate($domain, $id, $smartDataSources, $smartDataUnits);
        }
        return $this->return_json($response, $fullResult);
    }

    function unassociate(Request $request, Response $response, array $params) {
        # Basic validation
        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', null);
        if (! isset($smartdatacontextids)) {
            return $this->return_error($response, ['Missing smartDataContextIds'], 400);
        }
        if (! is_array($smartdatacontextids)) {
            $smartdatacontextids = [$smartdatacontextids];
        }
        $smartDataSources = $this->_get_value_or_default($body, 'smartDataSources', []);
        $smartDataUnits = $this->_get_value_or_default($body, 'smartDataUnits', []);

        $errors = array_merge(
            SmartDataContext::ValidateSmartDataSources($smartDataSources),
            SmartDataContext::ValidateSmartDataUnits($smartDataUnits));

        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $persistence = new SmartDataContextCrud($this->_get_db());
        $fullResult = [];
        foreach ($smartdatacontextids as $id) {
            $fullResult[] = $persistence->unassociate($domain, $id, $smartDataSources, $smartDataUnits);
        }

        return $this->return_json($response, $fullResult);
    }




    function get(Request $request, Response $response, array $params) {
        $domain = $this->_get_value_or_default($params, 'domain', null);
        $id = $this->_get_value_or_default($params, 'id', null);

        if (isset($domain) && isset($id)) {
            $persistence = new SmartDataContextCrud($this->_get_db());
            $result = $persistence->get(domain: $domain, id: $id);

            if ($result['success']) {
                return $this->return_json($response, $result["contents"]);
            } else {
                return $this->return_error($response, $result["errors"], 400);
            }
        } elseif (! $id) {
            return $this->return_error($response, ["missing SmartDataContextID"], 400);
        }
        elseif (! $domain) {
            return $this->return_error($response, ["missing domain"], 400);
        }
    }

    function contexts(Request $request, Response $response, array $params) {
        # Basic validation
        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $smartDataSources = $this->_get_value_or_default($body, 'smartDataSources', []);
        $smartDataUnits = $this->_get_value_or_default($body, 'smartDataUnits', []);

        if (count($smartDataSources) + count($smartDataUnits) == 0) {
            return $this->return_error($response, ["at least one smartDataSource or smartDataUnit must be provided"], 400);
        }

        $t0 = $this->_get_value_or_default($body, 't0', -1);
        $t1 = $this->_get_value_or_default($body, 't1', -1);

        $sourceFilter = [];
        foreach ($smartDataUnits as $smartDataUnit) {
            $sourceFilter[] = ["smartDataUnits" => $smartDataUnit];
        }
        foreach ($smartDataSources as $smartDataSource) {
            $sourceFilter[] = ["smartDataSources" => $smartDataSource];
        }
        $sourceFilter = ['$or' => $sourceFilter];

        $timeFilter = [];
        if (isset($t0) && isset($t1)) {
            $timeFilter = ['$and' => [['t0' => ['$lte' => $t0]], ['$or' => [['t1' => ['$gte' => $t1]], ["t1" => -1]]]]];
        }

        if ($timeFilter) {
            $filter = ['$and' => [$sourceFilter, $timeFilter]];
        } else {
            $filter = $sourceFilter;
        }

        $persistence = new SmartDataContextCrud($this->_get_db());
        $result = $persistence->query(domain: $domain, query: $filter);



        if ($result['success']) {
            return $this->return_json($response, $result["contents"]);
        } else {
            return $this->return_error($response, $result["errors"], 400);
        }
    }


    function query(Request $request, Response $response, array $params) {
        # Basic validation
        [$domain, $query, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $persistence = new SmartDataContextCrud($this->_get_db());
        $result = $persistence->query(domain: $domain, query: $query);

        if ($result['success']) {
            return $this->return_json($response, $result["contents"]);
        } else {
            return $this->return_error($response, $result["errors"], 400);
        }
    }

    private function _get_db() {
        return DBManager::GetMongoDBConnection();
    }

    private function return_json(Response $response, mixed $object, $status = 200) {
        $result = array("result" => $object);
        $response = $response->withAddedHeader("Content-Type", "application/json")->withStatus($status);
        $response->getBody()->write(json_encode($result));
        return $response;
    }

    private function return_error(Response $response, mixed $errorList, $status = 200) {
        $result = array("errors" => $errorList);        
        $response = $response->withAddedHeader("Content-Type", "application/json")->withStatus($status);
        $response->getBody()->write(json_encode($result));
        return $response;
    }

    private function _get_value_or_default($body, $key, $default) {
        if (! array_key_exists($key, $body)) {
            return $default;
        } else {
            return $body[$key];
        }
    }

    function update(Request $request, Response $response, array $params) {
        // Basic validation
        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        $id = $this->_get_value_or_default($params, 'id', null);
        if (!$id) {
            return $this->return_error($response, ["missing SmartDataContextID"], 400);
        }

        // Validate properties only present in the request body
        $updateData = [];
        if (isset($body['content'])) {
            $contentErrors = SmartDataContext::ValidateContent($body['content']);
            if ($contentErrors) {
                $errors = array_merge($errors, $contentErrors);
            }
            $updateData['content'] = $body['content'];
        }

        if (isset($body['features'])) {
            $featuresErrors = SmartDataContext::ValidateFeatures($body['features']);
            if ($featuresErrors) {
                $errors = array_merge($errors, $featuresErrors);
            }
            $updateData['features'] = $body['features'];
        }

        $t0Set = isset($body['t0']);
        $t1Set = isset($body['t1']);
        if ($t0Set && $t1Set) {
            $timestampErrors = SmartDataContext::ValidateTimestamp($body['t0'], $body['t1']);
            if ($timestampErrors) {
                $errors = array_merge($errors, $timestampErrors);
            }
            $updateData['t0'] = $body['t0'];
            $updateData['t1'] = $body['t1'];
        } elseif ($t0Set || $t1Set) {
            // Error if only one of t0 or t1 is set
            $errors[] = "Both t0 and t1 must be provided together.";
        }

        if (isset($body['smartDataSources'])) {
            $smartDataSourcesErrors = SmartDataContext::ValidateSmartDataSources($body['smartDataSources']);
            if ($smartDataSourcesErrors) {
                $errors = array_merge($errors, $smartDataSourcesErrors);
            }
            $updateData['smartDataSources'] = $body['smartDataSources'];
        }

        if (isset($body['smartDataUnits'])) {
            $smartDataUnitsErrors = SmartDataContext::ValidateSmartDataUnits($body['smartDataUnits']);
            if ($smartDataUnitsErrors) {
                $errors = array_merge($errors, $smartDataUnitsErrors);
            }
            $updateData['smartDataUnits'] = $body['smartDataUnits'];
        }

        if ($errors) {
            return $this->return_error($response, $errors, 400);
        }

        // Call the update method from SmartDataContextCrud
        $persistence = new SmartDataContextCrud($this->_get_db());
        $result = $persistence->update($domain, $id, $updateData);

        if ($result['success']) {
            return $this->return_json($response, $result['contents']);
        } else {
            return $this->return_error($response, $result['errors'], 400);
        }
    }

}