<?php

use \GuzzleHttp\Client;

$app = $this->app;

return [
    'name' => 'content',
    'label' => 'Content',
    'icon' => 'content:icon.svg',
    'fields' => (function() use($app) {

        $models = $app->module('content')->models();
        $options = [];

        foreach ($models as $model) {

            $options[] = [
                'value' => $model['name'],
                'label' => $model['label'] ? $model['label'] : $model['name'],
            ];
        }

        return [
            [
                'name' => 'locales',
                'type' => 'boolean',
            ],
            [
                'name' => 'models',
                'type' => 'select',
                'info' => 'Model schema',
                'opts' => [
                    'options' => $options,
                    'multiple' => true
                ],
            ],
            [
                'name' => 'data',
                'type' => 'select',
                'info' => 'Content items',
                'opts' => [
                    'options' => $options,
                    'multiple' => true
                ],
            ],
            [
                'name' => 'mirror',
                'type' => 'boolean',
                'info' => 'Mirror content items',
            ],
        ];
    })(),

    'sync:push' => function(array $settings = [], ?array $target = null, ?Client $client = null) {

        $settings = array_merge([
            'syncAll' => true,
            'locales' => false
        ], $settings);

        $models = $this->module('content')->models();

        if ($settings['syncAll']) {
            $settings['models'] = array_keys($models);
            $settings['data'] = $settings['models'];
        }

        if ($settings['syncAll'] || $settings['locales'] ) {

            $payload = [
                'locales' => $this->dataStorage->find('system/locales')->toArray()
            ];

            $this->helper('sync')->log('Pushing locales...');

            $client->request('POST', 'api/sync/job', [
                'json' => [
                    'job' => 'content',
                    'mode' => 'push',
                    'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
                ]
            ]);
        }

        if ($settings['syncAll'] || (isset($settings['models']) && is_array($settings['models']))) {

            $_models = [];

            foreach ($settings['models'] as $name) {
                if (isset($models[$name])) $_models[] = $models[$name];
            }

            if (count($_models)) {

                $payload = [
                    'models' => $_models
                ];

                $this->helper('sync')->log('Pushing models...');

                $client->request('POST', 'api/sync/job', [
                    'json' => [
                        'job' => 'content',
                        'mode' => 'push',
                        'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
                    ]
                ]);
            }
        }

        if ($settings['syncAll'] || (isset($settings['data']) && is_array($settings['data']))) {

            $singletons = array_filter(array_keys($models), fn($model) => $models[$model]['type'] == 'singleton' && in_array($model, $settings['data']));
            $collections = array_filter(array_keys($models), fn($model) => in_array($models[$model]['type'], ['collection', 'tree']) && in_array($model, $settings['data']));

            if (count($singletons)) {

                $this->helper('sync')->log('Pushing singletons...');

                $data = [];

                foreach ($singletons as $singleton) {

                    $item = $this->dataStorage->findOne('content/singletons', ['_model' => $singleton]);

                    if ($item) {
                        $data[$singleton] = $item;
                    }
                }

                $payload = [
                    'singletons' => $data
                ];

                $client->request('POST', 'api/sync/job', [
                    'json' => [
                        'job' => 'content',
                        'mode' => 'push',
                        'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
                    ]
                ]);
            }

            if (count($collections)) {

                $this->helper('sync')->log('Pushing collections...');

                foreach ($collections as $collection) {

                    $run = 0;
                    $limit = 20;

                    while (true) {

                        $items = $this->dataStorage->find("content/collections/{$collection}", [
                            'skip' => $run * $limit,
                            'limit' => $limit
                        ])->toArray();

                        $payload = [
                            'run' => $run,
                            'collection' => $collection,
                            'items' => $items,
                            'mirror' => !$settings['syncAll'] && ($settings['mirror'] ?? false),
                        ];

                        if (count($items)) {

                            $client->request('POST', 'api/sync/job', [
                                'json' => [
                                    'job' => 'content',
                                    'mode' => 'push',
                                    'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
                                ]
                            ]);
                        }

                        if (!count($items)) {
                            break;
                        }

                        $run += 1;
                    };
                }
            }
        }


    },

    'on:push' => function($payload = null) {

        if (isset($payload['locales']) && is_array($payload['locales'])) {

            foreach ($payload['locales'] as $locale) {

                if (!$this->dataStorage->findOne('system/locales', ['i18n' => $locale['i18n']])) {
                    unset($locale['_id']);
                    $this->trigger('app.locales.save', [&$locale, false]);
                    $this->dataStorage->save('system/locales', $locale);
                }
            }

            $this->helper('locales')->cache();
        }

        if (isset($payload['models']) && is_array($payload['models'])) {

            foreach ($payload['models'] as $model) {
                $this->module('content')->saveModel($model['name'], $model);
            }
        }

        if (isset($payload['singletons']) && is_array($payload['singletons'])) {

            foreach ($payload['singletons'] as $name => $data) {

                if (!$this->module('content')->exists($name)) continue;

                $this->dataStorage->remove('content/singletons', ['_model' => $name]);
                $this->dataStorage->insert('content/singletons', $data);
            }
        }

        if (isset($payload['collection'], $payload['items']) && is_array($payload['items']) && $this->module('content')->exists($payload['collection'])) {

            if (($payload['mirror'] ?? false) && $payload['run'] === 0) {
                $this->dataStorage->dropCollection("content/collections/{$payload['collection']}");
            }

            foreach ($payload['items'] as $item) {
                $this->dataStorage->remove("content/collections/{$payload['collection']}", ['_id' => $item['_id']]);
                $this->dataStorage->insert("content/collections/{$payload['collection']}", $item);
            }
        }

        return ['success' => true];
    },

    'sync:pull' => function(array $settings = [], ?array $target = null, ?Client $client = null) {

        $settings = array_merge([
            'syncAll' => true,
            'locales' => false
        ], $settings);

        $models = $this->module('content')->models();

        if ($settings['syncAll']) {
            $settings['models'] = array_keys($models);
            $settings['data'] = $settings['models'];
        }

        if ($settings['syncAll'] || $settings['locales'] ) {

            $this->helper('sync')->log('Pulling locales...');

            $response = $client->request('POST', 'api/sync/job', [
                'json' => [
                    'job' => 'content',
                    'mode' => 'pull',
                    'payload' => $this->helper('jwt')->encode([
                        'locales' => true
                    ], $target['syncKey'])
                ]
            ]);

            $payload = json_decode($response->getBody()->getContents(), true);

            if (isset($payload['locales']) && is_array($payload['locales'])) {

                foreach ($payload['locales'] as $locale) {

                    if (!$this->dataStorage->findOne('system/locales', ['i18n' => $locale['i18n']])) {
                        unset($locale['_id']);
                        $this->trigger('app.locales.save', [&$locale, false]);
                        $this->dataStorage->save('system/locales', $locale);
                    }
                }

                $this->helper('locales')->cache();
            }
        }

        if ($settings['syncAll'] || (isset($settings['models']) && is_array($settings['models']))) {

            $this->helper('sync')->log('Pulling models...');

            $response = $client->request('POST', 'api/sync/job', [
                'json' => [
                    'job' => 'content',
                    'mode' => 'pull',
                    'payload' => $this->helper('jwt')->encode([
                        'models' => $settings['syncAll'] ? true : $settings['models']
                    ], $target['syncKey'])
                ]
            ]);

            $payload = json_decode($response->getBody()->getContents(), true);

            if (isset($payload['models']) && is_array($payload['models'])) {

                foreach ($payload['models'] as $model) {
                    $this->module('content')->saveModel($model['name'], $model);
                }

                // include new synced models for data sync
                if ($settings['syncAll']) {
                    $models = $this->module('content')->models();
                    $settings['models'] = array_keys($models);
                    $settings['data'] = $settings['models'];
                }
            }
        }

        if ($settings['syncAll'] || (isset($settings['data']) && is_array($settings['data']))) {

            $singletons = array_filter(array_keys($models), fn($model) => $models[$model]['type'] == 'singleton' && in_array($model, $settings['data']));
            $collections = array_filter(array_keys($models), fn($model) => in_array($models[$model]['type'], ['collection', 'tree']) && in_array($model, $settings['data']));

            if (count($singletons)) {

                $this->helper('sync')->log('Pulling singletons...');

                $response = $client->request('POST', 'api/sync/job', [
                    'json' => [
                        'job' => 'content',
                        'mode' => 'pull',
                        'payload' => $this->helper('jwt')->encode([
                            'singletons' => $singletons
                        ], $target['syncKey'])
                    ]
                ]);

                $payload = json_decode($response->getBody()->getContents(), true);

                if (isset($payload['singletons']) && is_array($payload['singletons'])) {

                    foreach ($payload['singletons'] as $name => $data) {

                        if (!$this->module('content')->exists($name)) continue;

                        $this->dataStorage->remove('content/singletons', ['_model' => $name]);
                        $this->dataStorage->insert('content/singletons', $data);
                    }
                }
            }

            if (count($collections)) {

                $this->helper('sync')->log('Pulling collections...');

                foreach ($collections as $collection) {

                    $run = 0;

                    if ($settings['mirror'] ?? false) {
                        $this->dataStorage->dropCollection("content/collections/{$collection}");
                    }

                    while (true) {

                        $response = $client->request('POST', 'api/sync/job', [
                            'json' => [
                                'job' => 'content',
                                'mode' => 'pull',
                                'payload' => $this->helper('jwt')->encode([
                                    'collection' => $collection,
                                    'run' => $run
                                ], $target['syncKey'])
                            ]
                        ]);

                        $payload = json_decode($response->getBody()->getContents(), true);

                        if (!isset($payload['items']) || !count($payload['items'])) {
                            break;
                        }

                        foreach ($payload['items'] as $item) {
                            $this->dataStorage->remove("content/collections/{$collection}", ['_id' => $item['_id']]);
                            $this->dataStorage->insert("content/collections/{$collection}", $item);
                        }

                        $run += 1;
                    };
                }
            }

        }
    },

    'on:pull' => function($payload = null) {

        if (isset($payload['locales']) && $payload['locales']) {
            return ['locales' => $this->dataStorage->find('system/locales')->toArray()];
        }

        if (isset($payload['models'])) {

            $models = array_values($this->module('content')->models());

            if ($payload['models'] === true) {
                return ['models' => $models];
            }

            if (is_array($payload['models'])) {
                return ['models' => array_filter($models, fn($model) => in_array($model['name'], $payload['models']))];
            }
        }

        if (isset($payload['singletons']) && is_array($payload['singletons'])) {

            $data = [];

            $models = $this->module('content')->models();
            $singletons = array_filter(array_keys($models), fn($model) => $models[$model]['type'] == 'singleton' && in_array($model, $payload['singletons']));

            foreach ($singletons as $singleton) {

                $item = $this->dataStorage->findOne('content/singletons', ['_model' => $singleton]);

                if ($item) {
                    $data[$singleton] = $item;
                }
            }

            return ['singletons' => $data];
        }

        if (isset($payload['collection'], $payload['run']) && $this->module('content')->exists($payload['collection'])) {

            $run = $payload['run'];
            $limit = 20;

            $items = $this->dataStorage->find("content/collections/{$payload['collection']}", [
                'skip' => $run * $limit,
                'limit' => $limit
            ])->toArray();

            return ['items' => $items];
        }

        return ['success' => false];
    },

    'sync:push:item' => function(array $payload, ?array $target = null, ?Client $client = null) {

        if ($payload['model']['type'] == 'singleton') {
            $payload['item'] = $this->dataStorage->findOne('content/singletons', ['_model' => $payload['model']['name']]);
        } else {
            $payload['item'] = $this->dataStorage->findOne("content/collections/{$payload['model']['name']}", ['_id' => $payload['item']['_id']]);
        }

        $response = $client->request('POST', 'api/sync/item', [
            'json' => [
                'job' => 'content',
                'mode' => 'push',
                'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
            ]
        ]);

        if (isset($payload['syncAssets']) && $payload['syncAssets']) {

                $assets = $this->helper('sync')->getLinkedAssets($payload['item']);

                if (count($assets)) {

                    $client->request('POST', 'api/sync/job', [
                        'json' => [
                            'job' => 'assets',
                            'mode' => 'push',
                            'payload' => $this->helper('jwt')->encode([
                                'run' => 0,
                                'uploads' => $this->fileStorage->getURL('uploads://'),
                                'assets' => $assets
                            ], $target['syncKey'])
                        ]
                    ]);
                }
        }

        $response = json_decode($response->getBody()->getContents(), true);

        return $response;
    },

    'on:push:item' => function(array $payload) {

        if (!isset($payload['model'], $payload['item'])) {
            return ['success' => false];
        }

        if (!$this->module('content')->exists($payload['model']['name'])) {
            return ['success' => false, 'message' => "Model {$payload['model']['name']} does not exist"];
        }

        switch ($payload['model']['type']) {

            case 'singleton':

                $this->dataStorage->remove('content/singletons', ['_model' => $payload['model']['name']]);
                $this->dataStorage->insert('content/singletons', $payload['item']);

                break;

            case 'collection':

                $this->dataStorage->remove("content/collections/{$payload['model']['name']}", ['_id' => $payload['item']['_id']]);
                $this->dataStorage->insert("content/collections/{$payload['model']['name']}", $payload['item']);

                break;

            case 'tree':

                if (isset($payload['item']['_pid']) && $payload['item']['_pid']) {

                    $parent = $this->dataStorage->findOne("content/collections/{$payload['model']['name']}", ['_id' => $payload['item']['_pid']]);

                    if (!$parent) {
                        return ['success' => false, 'message' => 'Parent does not exist'];
                    }
                }

                $this->dataStorage->remove("content/collections/{$payload['model']['name']}", ['_id' => $payload['item']['_id']]);
                $this->dataStorage->insert("content/collections/{$payload['model']['name']}", $payload['item']);

                break;
        }

        return ['success' => true];
    },

    'sync:pull:item' => function(array $payload, ?array $target = null, ?Client $client = null) {

        $response = $client->request('POST', 'api/sync/item', [
            'json' => [
                'job' => 'content',
                'mode' => 'pull',
                'payload' => $this->helper('jwt')->encode($payload, $target['syncKey'])
            ]
        ]);

        $response = json_decode($response->getBody()->getContents(), true);

        if (!isset($response['item'])) {
            return ['success' => false, 'message' => 'Item not found'];
        }

        switch ($payload['model']['type']) {

                case 'singleton':

                    $this->dataStorage->remove('content/singletons', ['_model' => $payload['model']['name']]);
                    $this->dataStorage->insert('content/singletons', $response['item']);

                    break;

                case 'collection':

                    $this->dataStorage->remove("content/collections/{$payload['model']['name']}", ['_id' => $response['item']['_id']]);
                    $this->dataStorage->insert("content/collections/{$payload['model']['name']}", $response['item']);

                    break;

                case 'tree':

                    if (isset($response['item']['_pid']) && $response['item']['_pid']) {

                        $parent = $this->dataStorage->findOne("content/collections/{$payload['model']['name']}", ['_id' => $response['item']['_pid']]);

                        if (!$parent) {
                            return ['success' => false, 'message' => 'Parent does not exist'];
                        }
                    }

                    $this->dataStorage->remove("content/collections/{$payload['model']['name']}", ['_id' => $response['item']['_id']]);
                    $this->dataStorage->insert("content/collections/{$payload['model']['name']}", $response['item']);

                    break;
        }

        if (isset($payload['syncAssets']) && $response['item']) {

            $assets = $this->helper('sync')->getLinkedAssets($response['item']);

            $streamContext = stream_context_create( [
                'ssl' => [
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                ],
            ]);

            foreach ($assets as $asset) {

                $path = trim($asset['path'], '/');
                $url = "{$response['uploads']}/{$path}";

                if (!$this->fileStorage->fileExists("uploads://{$path}")) {

                    // check if resource exists
                    $headers = get_headers($url, 1, $streamContext);

                    if ($headers === false || !str_contains($headers[0], ' 200')) {
                        continue;
                    }

                    $stream = fopen($url, 'rb', false, $streamContext);

                    if ($stream === false) {
                        continue;
                    }

                    $this->fileStorage->writeStream("uploads://{$path}", $stream, ['mimetype' => $asset['mime']]);

                    if (is_resource($stream)) {
                        fclose($stream);
                    }
                }

                $this->dataStorage->remove('assets', ['_id' => $asset['_id']]);
                $this->dataStorage->insert('assets', $asset);
            }
        }

        return ['success' => true, 'item' => $response['item']];

    },

    'on:pull:item' => function(array $payload) {

        $item = null;

        switch ($payload['model']['type']) {

            case 'singleton':

                $item = $this->dataStorage->findOne('content/singletons', ['_model' => $payload['model']['name']]);

                break;

            case 'tree':
            case 'collection':

                $item = $this->dataStorage->findOne("content/collections/{$payload['model']['name']}", ['_id' => $payload['item']['_id']]);

                break;
        }

        return ['item' => $item, 'uploads' => $this->fileStorage->getURL('uploads://')];
    }
];
