<?php

namespace Common\Admin\Sitemap;

use Carbon\Carbon;
use Common\Core\Contracts\AppUrlGenerator;
use Common\Pages\CustomPage;
use File;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class BaseSitemapGenerator
{

    protected int $queryChunkSize = 1000;
    protected string $currentDateTimeString;
    protected int $currentResourceSitemapCount = 0;
    protected string | null $currentXml = null;
    protected int $currentLineCount = 0;

    public function __construct()
    {
        @ini_set('memory_limit', '160M');
        @ini_set('max_execution_time', 7200);
        $this->currentDateTimeString = Carbon::now()->toDateTimeString();
    }

    protected function getAppQueries(): array
    {
        return [];
    }

    protected function getAppStaticUrls(): array
    {
        return [];
    }

    public function generate(): void
    {
        $index = [];

        $queries = array_merge(
            [
                app(CustomPage::class)->select(['id', 'title', 'slug']),
            ],
            $this->getAppQueries(),
        );

        foreach ($queries as $query) {
            $resourceName = str_replace(
                '_',
                '-',
                $query->getModel()->getTable(),
            );
            $index[$resourceName] = $this->createSitemapForResource(
                $query,
                $resourceName,
            );
        }

        $this->makeStaticMap();
        $this->makeIndex($index);
    }

    protected function createSitemapForResource(
        Builder $model,
        string $name
    ): int {
        $model
            ->orderBy('id')
            ->chunk($this->queryChunkSize, function ($records) use ($name) {
                $modelName = ucfirst(Str::snake($records->first()::MODEL_TYPE));
                $methodName = "add{$modelName}Line";
                foreach ($records as $record) {
                    // allow extending class to override line adding for specific model
                    if (method_exists($this, $methodName)) {
                        $this->$methodName(
                            $this->getModelUrl($record),
                            $this->getModelUpdatedAt($record),
                            $name,
                        );
                    } else {
                        $this->addNewLine(
                            $this->getModelUrl($record),
                            $this->getModelUpdatedAt($record),
                            $name,
                        );
                    }
                }
            });

        if ($this->currentLineCount) {
            $this->save("$name-sitemap-{$this->currentResourceSitemapCount}");
        }

        $numberOfSitemapsGenerated = $this->currentResourceSitemapCount;

        $this->currentResourceSitemapCount = 0;
        $this->currentLineCount = 0;

        return $numberOfSitemapsGenerated;
    }

    protected function addNewLine(string $url, string $updatedAt, string $name)
    {
        if (!$this->currentXml) {
            $this->startNewXmlFile();
        }

        if ($this->currentLineCount === 50000) {
            $this->save("$name-sitemap-{$this->currentResourceSitemapCount}");
            $this->startNewXmlFile();
        }

        $updatedAt = $this->formatDate($updatedAt);

        $line =
            "\t" .
            "<url>\n\t\t<loc>" .
            htmlspecialchars($url) .
            "</loc>\n\t\t<lastmod>" .
            $updatedAt .
            "</lastmod>\n\t\t<changefreq>weekly</changefreq>\n\t\t<priority>1.00</priority>\n\t</url>\n";

        $this->currentXml .= $line;

        $this->currentLineCount++;
    }

    protected function save(string $fileName)
    {
        $this->currentXml .= "\n</urlset>";

        File::ensureDirectoryExists(public_path('storage/sitemaps'));
        File::put(
            public_path("storage/sitemaps/$fileName.xml"),
            $this->currentXml,
        );

        $this->currentXml = null;
        $this->currentLineCount = 0;
        $this->currentResourceSitemapCount++;
    }

    protected function startNewXmlFile()
    {
        $this->currentXml =
            '<?xml version="1.0" encoding="UTF-8"?>' .
            "\n" .
            '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">' .
            "\n";
    }

    protected function makeStaticMap(): void
    {
        $urls = array_merge(
            ['', 'login', 'register', 'contact'],
            $this->getAppStaticUrls(),
        );

        $urls = array_map(function ($data) {
            if (is_string($data)) {
                return [
                    'path' => $data,
                    'updated_at' => $this->currentDateTimeString,
                ];
            } else {
                return $data;
            }
        }, $urls);

        foreach ($urls as $url) {
            $this->addNewLine(
                url($url['path']),
                $url['updated_at'],
                'static-urls',
            );
        }

        $this->save('static-urls-sitemap');
    }

    protected function makeIndex(array $index): void
    {
        $baseUrl = url('storage/sitemaps');

        $string =
            '<?xml version="1.0" encoding="UTF-8"?>' .
            "\n" .
            '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' .
            "\n";

        foreach ($index as $resourceName => $resourceSitemapCount) {
            // sitemap count starts from 1
            for ($i = 0; $i <= $resourceSitemapCount - 1; $i++) {
                $url = "{$baseUrl}/{$resourceName}-sitemap-$i.xml";
                $string .=
                    "\t<sitemap>\n" .
                    "\t\t<loc>$url</loc>\n" .
                    "\t\t<lastmod>{$this->formatDate()}</lastmod>\n" .
                    "\t</sitemap>\n";
            }
        }

        $string .= "\t<sitemap>\n\t\t<loc>{$baseUrl}/static-urls-sitemap.xml</loc>\n\t\t<lastmod>{$this->formatDate()}</lastmod>\n\t</sitemap>\n";

        $string .= '</sitemapindex>';

        File::put(public_path('storage/sitemaps/sitemap-index.xml'), $string);
    }

    protected function getModelUrl(Model $model): string
    {
        $resourceName = Str::camel(class_basename($model));
        return app(AppUrlGenerator::class)->$resourceName($model);
    }

    protected function formatDate(string $date = null): string
    {
        if (!$date) {
            $date = $this->currentDateTimeString;
        }
        return date('Y-m-d\TH:i:sP', strtotime($date));
    }

    protected function getModelUpdatedAt(Model $model): string
    {
        return !$model->updated_at ||
            $model->updated_at === '0000-00-00 00:00:00'
            ? $this->currentDateTimeString
            : $model->updated_at;
    }
}
