<?php

namespace App\Services\Help;

use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
use RuntimeException;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Yaml\Yaml;

class HelpSyncService
{
    private MarkdownConverter $converter;

    public function __construct(private readonly Filesystem $files)
    {
        $environment = new Environment;
        $environment->addExtension(new CommonMarkCoreExtension);
        $environment->addExtension(new TableExtension);
        $this->converter = new MarkdownConverter($environment);
    }

    /**
     * @return array<string, array<string, int>>
     */
    public function sync(): array
    {
        $sourcePath = base_path(config('help.source_path'));

        if (! $this->files->exists($sourcePath)) {
            throw new RuntimeException('Help source directory not found: '.$sourcePath);
        }

        $articles = collect();

        $audiences = config('help.audiences', []);
        $localeDirectories = $this->files->directories($sourcePath);
        $useLocaleDirectories = ! empty($localeDirectories);

        $audienceRoots = $useLocaleDirectories
            ? array_map(static fn (string $path) => $path.DIRECTORY_SEPARATOR, $localeDirectories)
            : [$sourcePath.DIRECTORY_SEPARATOR];

        foreach ($audienceRoots as $root) {
            foreach ($audiences as $audience) {
                $audiencePath = $root.$audience;

                if (! $this->files->isDirectory($audiencePath)) {
                    continue;
                }

                $files = $this->files->allFiles($audiencePath);

                /** @var SplFileInfo $file */
                foreach ($files as $file) {
                    if ($file->getExtension() !== 'md') {
                        continue;
                    }

                    $parsed = $this->parseFile($file);
                    $articles->push($parsed);
                }
            }
        }

        $disk = config('help.disk');
        $compiledPath = trim(config('help.compiled_path'), '/');
        $written = [];

        foreach ($articles->groupBy(fn ($article) => $article['audience'].'::'.$article['locale']) as $key => $group) {
            [$audience, $locale] = explode('::', $key);
            $path = sprintf('%s/%s/%s/articles.json', $compiledPath, $audience, $locale);
            $directory = sprintf('%s/%s/%s', $compiledPath, $audience, $locale);
            Storage::disk($disk)->makeDirectory($directory);
            Storage::disk($disk)->put($path, $group->values()->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
            Cache::forget($this->cacheKey($audience, $locale));
            $written[$audience][$locale] = $group->count();
        }

        return $written;
    }

    private function parseFile(SplFileInfo $file): array
    {
        $contents = $this->files->get($file->getPathname());

        if (! Str::startsWith($contents, "---\n")) {
            throw new RuntimeException('Missing front matter in '.$file->getPathname());
        }

        $pattern = '/^---\s*\n(?P<yaml>.*?)-{3}\s*\n(?P<body>.*)$/s';

        if (! preg_match($pattern, $contents, $matches)) {
            throw new RuntimeException('Unable to parse front matter for '.$file->getPathname());
        }

        $frontMatter = Yaml::parse(trim($matches['yaml'] ?? '')) ?? [];
        $frontMatter = array_map(static fn ($value) => $value ?? null, $frontMatter);

        $this->validateFrontMatter($frontMatter, $file->getPathname());

        $body = trim($matches['body'] ?? '');
        $html = trim($this->converter->convert($body)->getContent());
        $updatedAt = now()->setTimestamp($file->getMTime())->toISOString();

        return array_merge($frontMatter, [
            'body_markdown' => $body,
            'body_html' => $html,
            'source_path' => $file->getRelativePathname(),
            'updated_at' => $updatedAt,
        ]);
    }

    private function validateFrontMatter(array $frontMatter, string $path): void
    {
        $required = config('help.required_front_matter', []);

        foreach ($required as $key) {
            if (! Arr::exists($frontMatter, $key)) {
                throw new RuntimeException(sprintf('Missing front matter key "%s" in %s', $key, $path));
            }
        }

        $audiences = config('help.audiences', []);
        $audience = $frontMatter['audience'];

        if (! in_array($audience, $audiences, true)) {
            throw new RuntimeException(sprintf('Invalid audience "%s" in %s', $audience, $path));
        }
    }

    private function cacheKey(string $audience, string $locale): string
    {
        return sprintf('help.%s.%s', $audience, $locale);
    }
}
