<?php

namespace Sync\Helper;

use Firebase\JWT\JWT;
use Closure;
use ErrorException;
use GuzzleHttp\Client;

class Sync extends \Lime\Helper {

    protected $tmpDir = null;
    protected $lockFile = null;

    protected function initialize() {

        $this->tmpDir = \rtrim($this->app->path('#tmp:'),"/\\");
        $this->lockFile = $this->tmpDir.'/sync.lock';
    }

    public function isSyncRunning() {
        return \file_exists($this->lockFile);
    }

    public function log(?string $msg = null) {

        if (!$this->isSyncRunning()) {
            return;
        }

        if (!\is_null($msg)) {
            return $this->app->helper('fs')->write($this->lockFile, "{$msg}\n", \FILE_APPEND);
        }

        return $this->app->helper('fs')->read($this->lockFile);
    }

    public function run(array $target, array $jobs) {

        if ($this->isSyncRunning()) {
            throw new ErrorException('A sync job is already running');
        }

        $target = [
            'uri' => $target['uri'] ?? null,
            'mode' => $target['mode'] ?? null,
            'syncKey' => $target['syncKey'] ?? null,
        ];

        if (!isset($target['uri'], $target['mode'], $target['syncKey'])) {
            throw new ErrorException('Target configuration issue');
        }

        $client = new Client([
            'base_uri' => \trim($target['uri'], '/').'/',
            'verify' => false
        ]);

        $res = $client->request('POST', 'api/sync/check', [
            'http_errors' => false
        ]);

        if ($res->getStatusCode() != 200) {
            return;
        }

        // create lock file
        $this->app->helper('fs')->write($this->lockFile, 'Sync started: '.\date('r')."\n");

        $this->log("{$target['uri']}, {$target['mode']}");
        $this->log("Jobs: ".\implode(', ', \array_map(fn($j) => $j['name'], $jobs))."\n");

        foreach ($jobs as $job) {

            $cfg = $this->app->module('sync')->jobs($job['name']);

            if (!$cfg || !isset($cfg["sync:{$target['mode']}"])) continue;

            $settings = \array_merge([
                'syncAll' => true
            ], $job['syncSettings'] ?? []);

            $callback = Closure::bind($cfg["sync:{$target['mode']}"], $this->app, $this->app);

            $this->log("Start Job: {$job['name']} (".\date('r').")");

            try {
                $callback($settings, $target, $client, $this);
            } catch (\Throwable $e) {
                $this->app->helper('fs')->delete($this->lockFile);
                $this->app->module('system')->log("Sync error: {$e->getMessage()}", channel: 'sync', type: 'error', context: $e);
                return;
            }

            $this->log("Job: {$job['name']} finished (".\date('r').")\n");
        }

        $this->app->trigger("sync.{$target['mode']}.finished", [$target]);

        $client->request('POST', 'api/sync/finished', [
            'json' => [
                'mode' => $target['mode'],
                'payload' => $this->app->helper('jwt')->encode([
                    'target' => $target,
                    'jobs' => $jobs
                ], $target['syncKey'])
            ],
            'http_errors' => false
        ]);

        // delete lock file
        $this->app->helper('fs')->delete($this->lockFile);
    }

    protected function stop() {

        if ($this->isSyncRunning()) {
            $this->app->helper('fs')->delete($this->lockFile);
        }
    }

    public function syncItem(string $job, array $target, array $payload) {

        $target = [
            'uri' => $target['uri'] ?? null,
            'mode' => $target['mode'] ?? null,
            'syncKey' => $target['syncKey'] ?? null,
        ];

        if (!isset($target['uri'], $target['mode'], $target['syncKey'])) {
            throw new ErrorException('Target configuration issue');
        }

        $client = new Client([
            'base_uri' => \trim($target['uri'], '/').'/',
            'verify' => false
        ]);

        $res = $client->request('POST', 'api/sync/check', [
            'http_errors' => false
        ]);

        if ($res->getStatusCode() != 200) {
            return ['error' => 'Target not available'];
        }

        $cfg = $this->app->module('sync')->jobs($job);

        if (!$cfg || !isset($cfg["sync:{$target['mode']}:item"])) {
            return ['error' => "Missing job handler sync:{$target['mode']}:item"];
        }

        $callback = Closure::bind($cfg["sync:{$target['mode']}:item"], $this->app, $this->app);
        $ret = $callback($payload, $target, $client);

        $this->app->trigger("sync.{$target['mode']}.finished", [$target]);

        $client->request('POST', 'api/sync/finished', [
            'json' => [
                'mode' => $target['mode'],
                'payload' => $this->app->helper('jwt')->encode([
                    'target' => $target,
                    'jobs' => [$job]
                ], $target['syncKey'])
            ],
            'http_errors' => false
        ]);

        return $ret;
    }

    public function getLinkedAssets(array $data, $collector = null) {

        if (!$collector) {
            $collector = new \ArrayObject([]);
        }

        if (isset($data['_id'], $data['path'], $data['mime'], $data['size'])) {
            $collector[$data['_id']] = $data;
        }

        foreach ($data as $key => $value) {
            if (\is_array($value)) $this->getLinkedAssets($value, $collector);
        }

        return \array_values($collector->getArrayCopy());
    }
}
