<?php

declare(strict_types=1);

use Carbon\Carbon;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Validation\Factory as FactoryContract;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Inside\Content\Contracts\RevisionService;
use Inside\Content\Contracts\SchemaService;
use Inside\Content\Contracts\SchemaService as SchemaServiceContract;
use Inside\Content\Contracts\Serializer;
use Inside\Content\Contracts\WysiwygImageService;
use Inside\Content\Facades\ContentHelper;
use Inside\Content\Models\Content;
use Inside\Content\Models\Revision;
use Inside\Host\Bridge\BridgeContent;
use Inside\Host\Migrations\CreateContentTypes;
use Inside\Validation\Validator;
use Inside\Workflow\Contracts\Workflow;

final class FixAllSections extends Migration
{
    use CreateContentTypes;

    protected SchemaServiceContract $schemaService;

    protected RevisionService $revisionService;

    protected BridgeContent $bridgeContent;

    protected Workflow $workflowService;

    protected Filesystem $storage;

    protected WysiwygImageService $wysiwygImageService;

    protected Serializer $serializerService;

    public function __construct()
    {
        $this->schemaService = App::make(SchemaServiceContract::class);
        $this->revisionService = App::make(RevisionService::class);
        $this->bridgeContent = App::make(BridgeContent::class);
        $this->workflowService = App::make(Workflow::class);
        $this->wysiwygImageService = App::make(WysiwygImageService::class);
        $this->serializerService = App::make(Serializer::class);
        $this->storage = Storage::disk('local');
    }

    public function up(): void
    {
        $this->bootUp(__FILE__, 'fix_all_sections');

        $bridge = $this->getBridge();
        $console = $this->getConsole();
        if (is_null($bridge)) {
            return;
        }

        $this->workflowService->disableWorkflow();
        $this->revisionService->disableRevision();

        $console->writeln('Vérification des champs "paragraphe" mal configurés');

        $contentTypes = [];

        foreach ($this->schemaService->getAllFieldsListingOfType('section') as $type => $fieldNames) {
            $console->writeln(sprintf(
                '<module>Pour le type %s</module>', $type
            ));
            foreach ($fieldNames as $fieldName) {
                $console->write(sprintf(
                    '=> <info>%s</info>', $fieldName
                ));
                try {
                    $options = $this->schemaService->getFieldOptions($type, $fieldName);
                    if (isset($options['translatable']) && $options['translatable'] === true) {
                        $console->write(' <note>[Déjà bon]</note>');
                        $this->writeResult(true);
                        continue;
                    }
                    $options['translatable'] = true;
                    $console->write(' <note>[à corriger]</note>');
                    $bridge->updateFieldOptions($type, $fieldName, $options);
                    $this->writeResult(true);
                    if ($this->schemaService->isSectionType($type)) {
                        continue;
                    }
                    $contentTypes[] = $type;
                } catch (Exception $exception) {
                    $console->write(sprintf(
                        ' <error> Échec %s</>', $exception->getMessage()
                    ));
                    $this->writeResult(false);
                }
            }
        }

        $console->writeln('Re-sauvegarde des contenus pour re-créer les paragraphes.');
        foreach ($contentTypes as $type) {
            // Re-save content !
            foreach (list_languages() as $language) {
                $console->writeln(sprintf(
                    '######## <info>%s en %s</info>',
                    $type, $language
                ));

                $query = call_user_func(type_to_class($type).'::withoutGlobalScopes');
                $query->where('langcode', $language);
                $query->orderBy('created_at');
                $query->each(function (Content $content) use ($type, $console) {
                    $revision = new Revision([
                        'contentable_type' => get_class($content),
                        'contentable_id' => $content->uuid,
                        'user_id' => $content->modificator?->uuid,
                        'locale' => $content->langcode,
                        'data' => $this->serializerService->serialize($content),
                        'version' => 1,
                        'created_at' => is_int($content->updated_at) ? Carbon::createFromTimestampUTC($content->updated_at) : null,
                    ]);

                    // Save without section
                    $data = $this->revisionService->transformContentFieldsWithTransformer(
                        $content,
                        $revision,
                        Inside\Content\Services\Revision\RevisionService::TRANSFORMER_TO_BRIDGE_CONTENT_DATA
                    );

                    $data = ContentHelper::addMissings($type, $data, false);
                    $data = ContentHelper::castAttributes($type, $data);
                    $data['uuid'] = $content->uuid;
                    $data['langcode'] = $content->langcode;
                    $data['status'] = $content->status;

                    $rules = ContentHelper::makeRules($type, $content->uuid);
                    try {
                        $data = $this->validateData(
                            $data,
                            $rules,
                            [],
                            ContentHelper::getAttributeNames($type)
                        );
                    } catch (ValidationException $exception) {
                        Log::warning(sprintf(
                            'Validation failed for %s <%s> with message %s',
                            $type,
                            $content->uuid ?? 'null',
                            $exception->getMessage()
                        ));

                        return;
                    }

                    $fieldNames = $this->schemaService->getFieldListingOfType($type, 'section');
                    $data = $this->removeSectionIdsFromData($type, $data);
                    try {
                        $sectionCount = $content->{'section_'.Arr::first($fieldNames).'_count'};
                        if ($sectionCount === 0) {
                            $console->write(sprintf(
                                ' <note>[Pas de paragraphe, pas besoin] <fg=cyan>%s</> => simple sauvegarde</note>',
                                $content->title ?? 'not set'
                            ));
                            $uuid = $this->bridgeContent->contentUpdate(
                                $type,
                                $data,
                                true
                            );
                            $this->writeResult($uuid === $content->uuid);
                            $console->writeln(str_repeat('-', 80));

                            return;
                        }
                        $console->write(sprintf(
                            '=> <info>Suppression des paragraphes de <fg=cyan>%s</></info>',
                            $content->title ?? 'not set'
                        ));
                        $uuid = $this->bridgeContent->contentUpdate(
                            $type,
                            array_merge(
                                Arr::except($data, $fieldNames),
                                array_map(fn () => [], array_flip($fieldNames))
                            ),
                            true
                        );
                        $this->writeResult($uuid === $content->uuid);

                        // Vérification
                        $console->write(sprintf(
                            '## <info>Vérification de la suppression de %s paragraphe:%s</info>',
                            $sectionCount, $sectionCount > 1 ? 's' : ''
                        ));
                        $savedContent = $content->fresh();
                        $newSectionCount = $savedContent->{'section_'.Arr::first($fieldNames).'_count'};
                        $this->writeResult($newSectionCount === 0);
                        if ($newSectionCount !== 0) {
                            Log::warning(sprintf(
                                'Failed to delete paragraph for content %s <:%s> actual count %s',
                                $type,
                                $content->uuid ?? 'null',
                                $sectionCount
                            ));
                        }
                        $console->write(sprintf(
                            '=> <info>Sauvegarde des paragraphes de <fg=cyan>%s</></info>',
                            $content->title ?? 'not set'
                        ));
                        $uuid = $this->bridgeContent->contentUpdate($type, $data, true);
                        $this->writeResult($uuid === $content->uuid);
                        $console->write(sprintf(
                            '## <info>Vérification de la récupération de %s paragraphe%s</info>',
                            $sectionCount,
                            $sectionCount > 1 ? 's' : ''
                        ));
                        $savedContent = $content->fresh();
                        $newSectionCount = $savedContent->{'section_'.Arr::first($fieldNames).'_count'};
                        $this->writeResult($newSectionCount === $sectionCount);
                        if ($newSectionCount != $sectionCount) {
                            Log::warning(sprintf(
                                'Failed to recreate paragraph for content %s <%s> actual count %s < wanted %s',
                                $type, $content->uuid ?? 'null',
                                $newSectionCount,
                                $sectionCount
                            ));
                        }
                        $console->writeln(str_repeat('-', 80));
                    } catch (Exception) {
                        $this->writeResult(false);

                        return;
                    }
                });
            }
        }
    }

    private function removeSectionIdsFromData(string $type, array $data): array
    {
        $fieldNames = $this->schemaService->getFieldListingOfType($type, 'section');
        foreach ($fieldNames as $fieldName) {
            if (array_key_exists($fieldName, $data) && is_array($data[$fieldName])) {
                foreach ($data[$fieldName] as $key => $section) {
                    if (isset($section['pgID'])) {
                        unset($data[$fieldName][$key]['pgID']);
                    }
                    if (isset($section['uuid'])) {
                        unset($data[$fieldName][$key]['uuid']);
                    }
                    if (isset($data[$fieldName][$key]['bundle'])) {
                        $data[$fieldName][$key] = $this->removeSectionIdsFromData(
                            $section['bundle'],
                            $data[$fieldName][$key]
                        );
                    }
                    $data[$fieldName][$key] = $this->switchSectionFile($section['bundle'], $data[$fieldName][$key]);
                }
            }
        }

        return $data;
    }

    private function switchSectionFile(string $type, array $data): array
    {
        $fieldNames = $this->schemaService->getFieldListingOfType($type, ['image', 'file']);

        foreach ($fieldNames as $fieldName) {
            if (array_key_exists($fieldName, $data) && is_string($data[$fieldName])) {
                if ($this->storage->exists($data[$fieldName])) {
                    $path = 'chunks/'.Str::uuid().'/';
                    $this->storage->copy($data[$fieldName], $path.$data[$fieldName]);
                    $data[$fieldName] = $path.$data[$fieldName];
                }
            }
        }

        return $data;
    }

    /**
     * @throws ValidationException
     */
    protected function validateData(
        array $data,
        array $rules,
        array $messages = [],
        array $customAttributes = []
    ): array {
        $factory = $this->getValidationFactory();
        $factory->resolver(
            function ($translator, $data, $rules, $messages, $customAttributes) {
                return new Validator($translator, $data, $rules, $messages, $customAttributes);
            }
        );

        return $factory->make(
            $data,
            $rules,
            $messages,
            $customAttributes
        )->validate();
    }

    protected function getValidationFactory(): FactoryContract
    {
        return app('validator');
    }
}
