<?php

namespace Ignite\Core\Database\Seeders;

use Ignite\Core\Contracts\Repositories\PageRepository;
use Ignite\Core\Entities\Page;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Blade;

/**
 * php artisan db:seed --class="Ignite\Core\Database\Seeders\PageTableSeeder"
 * php artisan module:seed Core
 */
class PageTableSeeder extends AbstractSeeder
{
    /**
     * The name of the data file.
     *
     * @var string
     */
    protected $file = 'core_page.json';

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        parent::run();

        $pageRepository = app(PageRepository::class);
        $filepath = $this->getFilepath($this->file);
        $strategy = $this->strategy('json', [
            'filepath' => $filepath
        ]);

        /** @var \Illuminate\Support\Collection $sources */
        $sources = $strategy->run()->sortBy('position');
        $sourceDirectory = dirname($filepath);

        foreach ($sources as $source) {
            try {
                $this->seedRow($pageRepository, $source, $sourceDirectory);
            } catch (\Exception $e) {
                echo $e->getMessage() . PHP_EOL;
                continue;
            }
        }
    }

    /**
     * Content only valid if newer than last update, otherwise it may too old to use.
     * To prevent accidental overwrite of existing newer content.
     *
     * @param  array     $data
     * @param  Page|null $page
     * @return boolean
     */
    protected function isStillValidUpdate(array $data, ?Page $page = null): bool
    {
        if (! $page) {
            return true;
        }

        $validIfLastUpdateBefore = $data['_valid_if_last_update_before_'] ?? null;

        return (null === $validIfLastUpdateBefore
            || Carbon::parse($validIfLastUpdateBefore) > $page->updated_at
        );
    }

    /**
     * @param  PageRepository $pageRepository
     * @param  array          $source
     * @param  string         $sourceDirectory
     */
    protected function seedRow(PageRepository $pageRepository, array $source, string $sourceDirectory)
    {
        $source = $this->hydrateContentFromSource($source, $sourceDirectory);

        $page = Page::where([
            "code" => $source['code'],
            "locale" => $source['locale'],
        ])
            ->first();

        $isStillValidUpdate = $this->isStillValidUpdate($source, $page);
        unset($source['_valid_if_last_update_before_']);

        if (isset($source['_delete_']) && true === $source['_delete_']) {
            if ($page) {
                $page->delete();
            }
        } elseif ($page) {
            if (! $isStillValidUpdate) {
                return;
            }
            unset($source['id']);
            $pageRepository->update($page->id, $source);
        } else {
            $source['published_at'] = now();
            $pageRepository->create($source);
        }
    }

    /**
     * If `content` is empty, optionally hydrate page content from a blade/html source file.
     * Blade and inline PHP are rendered before the content is stored.
     *
     * @param  array  $source
     * @param  string $sourceDirectory
     * @return array
     */
    protected function hydrateContentFromSource(array $source, string $sourceDirectory): array
    {
        $isEmptyContent = ! isset($source['content']) || trim((string) $source['content']) === '';
        $hasSourceFile = isset($source['content_source']) && trim((string) $source['content_source']) !== '';

        if ($isEmptyContent && $hasSourceFile) {
            $contentSource = ltrim((string) $source['content_source'], DIRECTORY_SEPARATOR);
            $sourceFile = $sourceDirectory . DIRECTORY_SEPARATOR . $contentSource;

            if (! file_exists($sourceFile)) {
                $fallbackDirectory = realpath(__DIR__ . '/../../../database/data');
                if ($fallbackDirectory !== false) {
                    $fallbackFile = $fallbackDirectory . DIRECTORY_SEPARATOR . $contentSource;
                    if (file_exists($fallbackFile)) {
                        $sourceFile = $fallbackFile;
                    }
                }
            }

            if (! file_exists($sourceFile)) {
                throw new \RuntimeException(sprintf(
                    'Content source not found for page [%s]: %s',
                    $source['code'] ?? '',
                    $sourceFile
                ));
            }

            $content = file_get_contents($sourceFile);

            if ($content === false) {
                throw new \RuntimeException(sprintf(
                    'Unable to read content source for page [%s]: %s',
                    $source['code'] ?? '',
                    $sourceFile
                ));
            }

            $source['content'] = Blade::render($content);
        }

        unset($source['content_source']);

        return $source;
    }
}
