<?php

namespace Inside\IBCA\WebHelp;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Inside\Host\Bridge\BridgeContent;
use Inside\IBCA\Console\ImportCommand;
use Inside\Authentication\Models\User;
use Orchestra\Parser\Xml\Facade as XmlParser;
use Stevebauman\Purify\Facades\Purify;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Throwable;
use Vaites\ApacheTika\Client;

/**
 * IBCA Webhelp Importer.
 *
 * @category Class
 * @package  Inside\IBCA\WebHelp\Importer
 * @author   Maecia <contact@feldoe.net>
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @link     http://www.maecia.com/
 */
class Importer
{
    protected string $separator;

    protected array $categories = [];

    public function __construct(
        protected BridgeContent $bridge
    ) {
        $this->separator = config('app.ged.separator', ' / ');
    }

    public function import(?ImportCommand $command = null, bool $documents = false, bool $hrDocuments = false, bool $robohelpDocuments = false): int
    {
        if (!$documents && !$robohelpDocuments && !$hrDocuments) {
            $hrDocuments = $documents = $robohelpDocuments = true;
        }

        $path = config('app.ged.root', '').config('app.ged.webhelp', '').'/';

        $command?->info('Loading index ...');

        $toBeInserted = $this->load($path);

        $command?->info('['.$toBeInserted->count().'] documentation categories');

        // Find super_admin user, he'll be author for everything we import
        try {
            /** @var User $admin */
            $admin = User::where('name', config('app.ged.author', 'super_admin'))->firstOrFail();
        } catch (Throwable) {
            $command?->error('We can\'t find user ['.config(
                'app.ged.author',
                'super_admin'
            ).'] check your configuration!');
            exit(-1);
        }

        $command?->info('Importing will be attached to user '.$admin->name);

        $count = 0;
        $index = 1;

        // Create or Update everything
        foreach ($toBeInserted as $category => $items) {
            if (
                (in_array($index, config('app.ged.documents_robohelp', [])) && !$robohelpDocuments) ||
                (in_array($index, config('app.ged.documents', [])) && !$documents) ||
                (in_array($index, config('app.ged.documents_hr', [])) && !$hrDocuments)
            ) {
                $index++;
                continue; // We don't need to import this category
            }

            if ($command) {
                $console = $command->getOutput();
                $bar = $console->createProgressBar(count($items));

                if (in_array($index, config('app.ged.documents_robohelp', []))) {
                    $bar->setFormat("<fg=yellow;bg=blue>%message%</>\n %current%/%max% [%bar%] %percent:3s%%");
                    $command->info('Importing robohelp category ['.$category.']');
                } elseif (in_array($index, config('app.ged.documents_hr', []))) {
                    $bar->setFormat("<fg=white;bg=black>%message%</>\n %current%/%max% [%bar%] %percent:3s%%");
                    $command->info('Importing hr category ['.$category.'] to GED');
                } else {
                    $bar->setFormat("<fg=black;bg=white>%message%</>\n %current%/%max% [%bar%] %percent:3s%%");
                    $command->info('Importing documents category ['.$category.'] to GED');
                }
            }

            foreach ($items as $item) {
                $fullpath = $path.config('app.ged.merged', '').'/'.$this->categories[$category].'/'.str_replace("\\", "/", $item['link']);
                $fullpath = preg_replace("/#.*$/", '', $fullpath);

                if ($fullpath && File::exists($fullpath)) {
                    if (isset($bar)) {
                        $bar->setMessage($item['name']);
                    }

                    $categories = collect($item['path']);

                    if (in_array($index, config('app.ged.documents_robohelp', [])) && $robohelpDocuments) {
                        // This is the only searchable document
                        $then = microtime(true);
                        Log::debug("Creating or update entity");

                        $datas = [
                            'type' => 'node',
                            'bundle' => 'documents_robohelp',
                            'uid' => $admin->getKey(),
                            'title' => $item['name'],
                            'longname' => $categories->count() >= 3 ? $categories->take(-($categories->count() - 2))->implode(" - ") : $item['name'],
                            'fullname' => $item['fullpath'],
                            'keywords' => $item['keywords'],
                            'filename' => str_replace('\\', '/', $item['link']),
                            'rd_file' => $fullpath,
                            'md5' => md5_file($fullpath),
                        ];

                        $query = call_user_func('\Inside\Content\Models\Contents\\'.studly_case('documents_robohelp').'::query');
                        $file = $query->where('fullname', $item['fullpath'])
                            ->first();

                        if ($file) {
                            $datas['uuid'] = $file->uuid;
                        }

                        try {
                            if (null === $this->bridge->contentInsert('documents_robohelp', $datas)) {
                                if ($command) {
                                    $command->error('Failed to Create / Insert Drupal Node for documentation ['.$item['name'].']');

                                    exit(-1);
                                }
                            }
                        } catch (\Exception $e) {
                            Log::error($e->getMessage());
                        }

                        $now = microtime(true);

                        Log::debug("Created/Updated Drupal content in [".sprintf("Elapsed:  %f", $now - $then)."]");
                    } elseif (
                        (in_array($index, config('app.ged.documents', [])) && $documents) ||
                        (in_array($index, config('app.ged.documents_hr', [])) && $hrDocuments)
                    ) {
                        $then = microtime(true);

                        $contentType = in_array($index, config('app.ged.documents', [])) ? 'documents' : 'documents_hr';
                        $folderType = in_array($index, config('app.ged.documents', [])) ? 'folders' : 'folders_hr';


                        try {
                            $tocreate = $categories->slice(0, -1);
                            $folder = $this->manageCategory($tocreate, $admin, $folderType);

                            $datas = [
                                'type' => 'node',
                                'bundle' => $contentType,
                                'uid' => $admin->getKey(),
                                'title' => $item['name'],
                                $folderType => [$folder],
                                'longname' => $categories->count() >= 3 ? $categories->take(-($categories->count() - 2))->implode(' - ') : $item['name'],
                                'fullname' => $item['fullpath'],
                                'keywords' => $item['keywords'],
                                'filename' => str_replace('\\', '/', $item['link']),
                                'md5' => md5_file($fullpath),
                                'content' => [
                                    [
                                        'body' => $this->getContent($fullpath),
                                        'bundle' => 'text',
                                        'author' => 1,
                                    ],
                                ],
                            ];
                            $query = call_user_func('\Inside\Content\Models\Contents\\'.studly_case($contentType).'::query');
                            $file = $query->where('fullname', $item['fullpath'])
                                ->first();

                            if ($file) {
                                $datas['uuid'] = $file->uuid;
                            }

                            if (null === $this->bridge->contentUpdate($contentType, $datas)) {
                                if ($command) {
                                    $command->error('Failed to Create / Insert Drupal Node for documentation ['.$item['name'].']');
                                    exit(-1);
                                }
                            }
                        } catch (\Exception $e) {
                            Log::error($e->getMessage());
                        }

                        $now = microtime(true);
                        Log::debug("Created/Updated Drupal content in [".sprintf("Elapsed:  %f", $now - $then)."]");
                    }
                    $count++;
                }

                if ($command && isset($bar)) {
                    $bar->advance();
                }
            }

            if ($command && isset($bar)) {
                $bar->setMessage("DONE");
                $bar->finish();
                $command->line('');
            }

            $index++;
        }

        return $count;
    }

    /**
     * load nice array of documents from the WebHelp structure
     *
     * @param string $path path to WebHelp files
     *
     * @return Collection
     */
    protected function load(string $path): Collection
    {
        $xml = XmlParser::load($path.config('app.ged.summary', 'summary.xml'));

        // Get the summary
        $summary = $xml->parse([
            'toc' => [
                'uses' => 'item[::name>name,::merge2>path]',
            ],
        ]);

        $tobeinserted = collect();
        foreach ($summary['toc'] as $category) {
            $finder = new Finder();
            $finder->files()->name('*.hhc')->in($path.config('app.ged.merged', '').'/'.$category['path']);

            foreach ($finder as $file) {
                $indexed = $this->loadIndex(str_replace(['hhc', 'sommaire'], ['hhk', 'index'], $file->getRealPath()));

                // Load summary and parse it to insert stuff
                $xml = @simplexml_load_file($file->getRealPath()) ?: [];
                foreach ($xml as $item) {
                    if ($item->getName() === 'item' && $item->attributes()) { // Only get items
                        $categoryName = (string)$item->attributes()->name;
                        $children = collect();

                        if (!array_key_exists($categoryName, $this->categories)) {
                            $this->categories[$categoryName] = $category['path'];
                        }

                        // Parse recursively children
                        $this->getChildren($children, $item->item, $indexed, [$categoryName], $categoryName);

                        // Build category with those children
                        $tobeinserted[$categoryName] = $children;
                    }
                }
            }
        }

        return $tobeinserted;
    }

    /**
     * Load all files
     *
     * @param Collection $children
     * @param \SimpleXMLElement $element
     * @param array $indexed
     * @param array $path
     * @param string $prefix
     */
    protected function getChildren(
        Collection $children,
        \SimpleXMLElement $element,
        array $indexed,
        array $path,
        string $prefix = ''
    ): void {
        foreach ($element as $item) {
            $attributes = $item->attributes();

            if (!$item->attributes()) {
                continue;
            }

            $name = (string)$item->attributes()->name;
            $newpath = collect($path)->push($name);

            if (isset($attributes['link'])) {
                $link = (string)$item->attributes()->link;
                $children[] = [
                    'fullpath' => $prefix.$this->separator.$name,
                    'name' => $name,
                    'link' => $link,
                    'path' => $newpath->toArray(),
                    'keywords' => implode(' ', array_key_exists($link, $indexed) ? $indexed[$link] : []),
                ];
                continue;
            }

            $this->getChildren($children, $item->item, $indexed, $newpath->toArray(), $prefix.$this->separator.$name);
        }
    }

    /**
     * @param string $path
     *
     * @return array
     * @deprecated
     */
    protected function loadSimpleSummary(string $path): array
    {
        $xml = XmlParser::load($path);

        // Get the summary for this category
        $summary = $xml->parse([
            'toc' => [
                'uses' => [
                    'item[::name>name,item{::name>name,item{::name>name,::link>link,item{::name>name,::link>link}}}]',
                ],
            ],
        ]);

        return $summary;
    }

    /**
     * load index from index file and build a nice array
     * indexed by file path
     *
     * @param string $path
     *
     * @return array
     */
    protected function loadIndex(string $path): array
    {
        $xml = XmlParser::load($path);
        $index = $xml->parse([
            'index' => [
                'uses' => [
                    'item[::name>keywords,section::name>name,section::link>link]',
                ],
            ],
        ]);

        $indexed = [];
        foreach ($index['index'][0] as $ind) {
            if (array_key_exists('link', $ind)) {
                if (!array_key_exists($ind['link'], $indexed)) {
                    $indexed[$ind['link']] = [];
                }

                $indexed[$ind['link']][] = $ind['keywords'];
            }
        }

        return $indexed;
    }

    /**
     * get The content of the file
     *
     * @param string|null $path
     *
     * @return string
     * @throws \Exception
     */
    protected function getContent(?string $path): string
    {
        if (empty($path)) {
            return '';
        }

        $client = Client::make(config('tika.host', 'localhost'), config('tika.port', 9998));

        if (!File::exists($path)) {
            $path = DRUPAL_ROOT.$path; // Relative path to root path
        }

        Log::debug("[BCA Importer] file path [$path] DRUPAL_ROOT [".DRUPAL_ROOT."]");
        $dir = str_replace(DRUPAL_ROOT, '', dirname($path));
        try {
            $text = $client->getHtml(\Drupal::service('file_system')->realpath($path));
        } catch (\Exception $e) {
            $text = '';
        }

        // Clean summaries
        $filename = basename($path);
        $text = preg_replace('/href="(#[^"]+)"/', "rel=\"$filename$1\"", $text ?? '');

        /** @var string $cleaned */
        $cleaned = Purify::clean(
            $text,
            [
                'URI.MakeAbsolute' => true,
                'URI.Base' => $dir.'/',
            ]
        );

        return $cleaned;
    }

    /**
     * @param Collection $categories
     * @param User $author
     * @param string $type
     * @return null|string
     * @throws \Exception
     */
    protected function manageCategory(Collection $categories, User $author, string $type): ?string
    {
        $parent = null;

        foreach ($categories as $category) {
            $query = call_user_func('\Inside\Content\Models\Contents\\'.studly_case($type).'::query');

            if (is_null($parent)) {
                $query->whereNull('pid');
            } else {
                $query->where('pid', $parent);
            }

            $query->where('title', $category);

            $folder = $query
                ->first();

            if (!$folder) {
                $data = [
                    'type' => 'node',
                    'bundle' => $type,
                    'uid' => $author->getKey(),
                    'title' => $category,
                    $type => [$parent],
                ];
                // dump($data);
                $parent = $this->bridge->contentInsert($type, $data);
                // dd($parent);
                continue;
            }

            $parent = $folder->uuid;
        }

        return $parent;
    }
}
