<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Model;

use idoit\Model\Dao\Base;
use idoit\Module\SyneticsFlows\Automation\Execution;
use idoit\Module\SyneticsFlows\Controller\SearchParams;
use idoit\Module\SyneticsFlows\Serialization\Serializer;
use isys_format_json;

class ExecutionDao extends Base
{
    public const LOCK_MODE_READ = 1;
    public const LOCK_MODE_UPDATE = 2;

    public const EXECUTION_FIELDS = [
        'execution.id' => 'id',
        'flow.title' => 'flowTitle',
        'execution.isys_flow_execution__isys_flow_automation__id' => 'flowId',
        'execution.status' => 'status',
        'execution.started' => 'started',
        'execution.finished' => 'finished',
        'execution.time' => 'time',
        'execution.execution_invocation' => 'invocation',
        'execution.execution_execution' => 'execution',
        'execution.execution_result' => 'result',
        'execution.automation_title' => 'automationTitle',
    ];
    public const ENTITY_FIELDS = [
        'id' => 'id',
        'isys_flow_execution__isys_flow_automation__id' => 'flowId',
        'status' => 'status',
        'started' => 'started',
        'finished' => 'finished',
        'time' => 'time',
        'execution_invocation' => 'invocation',
        'execution_execution' => 'execution',
        'execution_result' => 'result',
        'automation_title' => 'flowTitle',
    ];

    public const SORTING_MAP = [
        'id' => 'execution.id',
        'time' => 'execution.time',
        'flowId' => 'execution.automation_title',
        'flowTitle' => 'execution.automation_title',
        'status' => 'execution.status',
    ];

    public function getData(?string $condition = null, ?SearchParams $params = null, ?int $lockMode = null): array
    {
        $result = [];
        $query = 'SELECT ' . $this->selectImplode(self::EXECUTION_FIELDS) . ' FROM isys_flow_execution execution LEFT JOIN isys_flow_automation flow ON flow.id = isys_flow_execution__isys_flow_automation__id WHERE TRUE ';
        if ($condition) {
            $query .= $condition;
        }
        if ($params) {
            $sortId = $params->getSort()?->getId() ?? null;
            if ($sortId && isset(self::SORTING_MAP[$sortId])) {
                $direction = $params->getSort()?->isDesc() ? 'desc' : 'asc';
                $query .= ' ORDER BY ' . self::SORTING_MAP[$sortId] . ' ' . $direction . ', execution.id DESC';
            } else {
                $query .= ' ORDER BY execution.id DESC';
            }
            $query .= " LIMIT {$params->getPerPage()} OFFSET {$params->getOffset()}";
        }

        switch ($lockMode) {
            case self::LOCK_MODE_UPDATE:
                $query .= ' ON UPDATE';
                break;
            case self::LOCK_MODE_READ:
                $query .= ' LOCK IN SHARE MODE';
                break;
        }
        $daoResult = $this->retrieve($query . ';');
        while ($row = $daoResult->get_row()) {
            $data = [];
            foreach (self::EXECUTION_FIELDS as $field) {
                $data[$field] = match ($field) {
                    'invocation', 'execution', 'result' => is_string($row[$field]) ? json_decode($row[$field], true) : null,
                    default => $row[$field],
                };
            }
            $result[] = Serializer::fromJson(Execution::class, $data);
        }

        return $result;
    }

    public function getCount(?string $condition = null): int
    {
        $daoResult = $this->retrieve('SELECT count(1) as count FROM isys_flow_execution execution WHERE TRUE ' . ($condition ?: '') . ';');
        $result = $daoResult->get_row_value('count');
        if ($result === null) {
            return 0;
        }
        return intval($result);
    }

    public function get(string $id, ?int $lockMode = null): ?Execution
    {
        return $this->getData(' AND execution.id = ' . $this->convert_sql_id($id), new SearchParams(0, 1), $lockMode)[0] ?? null;
    }

    public function save(Execution $execution): ?string
    {
        $current = $execution->getId() ? $this->get($execution->getId()) : null;

        if ($current) {
            $sql = 'UPDATE isys_flow_execution SET %s WHERE id = ' . $this->convert_sql_id($current->getId()) . ';';
        } else {
            $sql = 'INSERT INTO isys_flow_execution SET %s;';
        }
        $data = $execution->jsonSerialize();
        $params = [];

        foreach (self::ENTITY_FIELDS as $dbField => $field) {
            switch ($field) {
                case 'time':
                case 'started':
                case 'finished':
                case 'status':
                    $params[] = $dbField . ' = ' . $this->convert_sql_text($data[$field]);
                    break;
                case 'flowId':
                    $params[] = $dbField . ' = ' . $this->convert_sql_int($data[$field]);
                    break;
                case 'invocation':
                case 'execution':
                case 'result':
                    $params[] = $dbField . ' = ' . $this->convert_sql_text(isys_format_json::encode($data[$field]));
                    break;
                case 'flowTitle':
                    $params[] = $dbField . ' = ' . $this->convert_sql_text($data['automationTitle'] ?? $data[$field]);
                    break;
            }
        }

        if (!empty($params) && $this->update(str_replace('%s', implode(',', $params), $sql)) && $this->apply_update()) {
            $id = strval($this->get_last_insert_id());
            if (!$execution->getId()) {
                $execution->setId("$id");
            }
            return $id;
        }

        return $execution->getId();
    }

    public function remove(string $id): bool
    {
        return $this->update('DELETE FROM isys_flow_execution WHERE id = ' . $this->convert_sql_text($id))
            && $this->apply_update();
    }
}
