<?php

namespace Botble\Theme\Services;

use Botble\Base\Facades\BaseHelper;
use Botble\Base\Supports\Helper;
use Botble\PluginManagement\Services\PluginService;
use Botble\Setting\Models\Setting;
use Botble\Setting\Supports\SettingStore;
use Botble\Theme\Events\ThemeRemoveEvent;
use Botble\Theme\Facades\Manager as ThemeManager;
use Botble\Theme\Facades\Theme;
use Botble\Theme\Facades\ThemeOption;
use Botble\Widget\Models\Widget;
use Carbon\Carbon;
use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class ThemeService
{
    public function __construct(
        protected Filesystem $files,
        protected SettingStore $settingStore,
        protected PluginService $pluginService
    ) {
    }

    public function activate(string $theme): array
    {
        $validate = $this->validate($theme);

        if ($validate['error']) {
            return $validate;
        }

        if (setting('theme') && $theme == Theme::getThemeName()) {
            return [
                'error' => true,
                'message' => trans('packages/theme::theme.theme_activated_already', ['name' => $theme]),
            ];
        }

        $config = $this->getThemeConfig($theme);
        $inheritTheme = Arr::get($config, 'inherit');

        try {
            $content = BaseHelper::getFileData($this->getPath($theme, 'theme.json'));

            if ($inheritTheme && ! Theme::exists($inheritTheme)) {
                return [
                    'error' => true,
                    'message' => trans('packages/theme::theme.theme_inherit_not_found', ['name' => $inheritTheme]),
                ];
            }

            if (! empty($content)) {
                $requiredPlugins = Arr::get($content, 'required_plugins', []);
                if (! empty($requiredPlugins)) {
                    foreach ($requiredPlugins as $plugin) {
                        $this->pluginService->activate($plugin);
                    }
                }
            }
        } catch (Exception $exception) {
            return [
                'error' => true,
                'message' => $exception->getMessage(),
            ];
        }

        $this->copyThemeOptions($theme);
        $this->copyThemeWidgets($theme);

        Theme::setThemeName($theme);

        $published = $this->publishAssets($theme);

        if ($published['error']) {
            return $published;
        }

        $this->settingStore
            ->forceSet('theme', $theme)
            ->save();

        Helper::clearCache();

        ThemeManager::clearCache();

        return [
            'error' => false,
            'message' => trans('packages/theme::theme.active_success', ['name' => $theme]),
        ];
    }

    public function copyThemeOptions(string $theme): void
    {
        $fromTheme = setting('theme');

        if ($fromTheme === $theme) {
            return;
        }

        $fromPrefix = 'theme-' . $fromTheme . '-';
        $toPrefix = 'theme-' . $theme . '-';

        $excludedPrefixes = $this->getRelatedThemePrefixes($fromTheme, 'theme-');

        $themeOptions = ThemeOption::getOptions();

        $themeOptions = collect($themeOptions)
            ->filter(function (mixed $value, string $key) use ($fromPrefix, $excludedPrefixes) {
                if (! Str::startsWith($key, $fromPrefix)) {
                    return false;
                }

                foreach ($excludedPrefixes as $excludedPrefix) {
                    if (Str::startsWith($key, $excludedPrefix)) {
                        return false;
                    }
                }

                return true;
            })
            ->toArray();

        $copiedThemeOptions = [];

        $now = Carbon::now();

        foreach ($themeOptions as $key => $option) {
            $key = $toPrefix . Str::after($key, $fromPrefix);

            $copiedThemeOptions[] = [
                'key' => $key,
                'value' => $option,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }

        if (! empty($copiedThemeOptions)) {
            Setting::query()
                ->insertOrIgnore($copiedThemeOptions);
        }
    }

    public function copyThemeWidgets(string $theme): void
    {
        $fromTheme = setting('theme');

        if ($fromTheme === $theme) {
            return;
        }

        $relatedThemes = $this->getRelatedThemeNames($fromTheme);

        $query = Widget::query()
            ->where(function ($query) use ($fromTheme): void {
                $query->where('theme', $fromTheme)
                    ->orWhere('theme', 'LIKE', $fromTheme . '-%');
            });

        if (! empty($relatedThemes)) {
            $query->where(function ($query) use ($relatedThemes): void {
                foreach ($relatedThemes as $relatedTheme) {
                    $query->where('theme', '!=', $relatedTheme)
                        ->where('theme', 'NOT LIKE', $relatedTheme . '-%');
                }
            });
        }

        $copiedWidgets = $query->get()->toArray();

        $fromPrefix = $fromTheme . '-';
        $toPrefix = $theme . '-';

        foreach ($copiedWidgets as $key => $widget) {
            $widgetTheme = $widget['theme'];
            if ($widgetTheme === $fromTheme) {
                $copiedWidgets[$key]['theme'] = $theme;
            } else {
                $copiedWidgets[$key]['theme'] = $toPrefix . Str::after($widgetTheme, $fromPrefix);
            }
            $copiedWidgets[$key]['data'] = json_encode($widget['data']);
            unset($copiedWidgets[$key]['id']);
        }

        if (! empty($copiedWidgets)) {
            Widget::query()
                ->insertOrIgnore($copiedWidgets);
        }
    }

    protected function validate(string $theme): array
    {
        $location = theme_path($theme);

        if (! $this->files->isDirectory($location)) {
            return [
                'error' => true,
                'message' => trans('packages/theme::theme.theme_is_not_existed'),
            ];
        }

        if (! $this->files->exists($location . '/theme.json')) {
            return [
                'error' => true,
                'message' => trans('packages/theme::theme.missing_json_file'),
            ];
        }

        return [
            'error' => false,
            'message' => trans('packages/theme::theme.theme_invalid'),
        ];
    }

    protected function copyDirectoryWithoutOverwriting(string $source, string $destination): void
    {
        if (! $this->files->isDirectory($source)) {
            return;
        }

        if (! $this->files->isDirectory($destination)) {
            $this->files->makeDirectory($destination, 0755, true);
        }

        $items = new \FilesystemIterator($source, \FilesystemIterator::SKIP_DOTS);

        foreach ($items as $item) {
            $target = $destination . '/' . $item->getBasename();

            if ($item->isDir()) {
                $this->copyDirectoryWithoutOverwriting($item->getPathname(), $target);
            } elseif (! $this->files->exists($target)) {
                $this->files->copy($item->getPathname(), $target);
            }
        }
    }

    protected function getPath(string $theme, ?string $path = null): string
    {
        return rtrim(theme_path(), '/') . '/' . rtrim(ltrim(strtolower($theme), '/'), '/') . '/' . $path;
    }

    public function publishAssets(?string $theme = null): array
    {
        if ($theme) {
            $themes = [$theme];
        } else {
            $themes = BaseHelper::scanFolder(theme_path());
        }

        foreach ($themes as $key => $theme) {
            if (! $this->files->isDirectory(theme_path($theme))) {
                unset($themes[$key]);

                continue;
            }

            $resourcePath = $this->getPath($theme, 'public');

            $themePath = public_path('themes');
            if (! $this->files->isDirectory($themePath)) {
                $this->files->makeDirectory($themePath, 0755, true);
            } elseif (! $this->files->isWritable($themePath)) {
                return [
                    'error' => true,
                    'message' => trans('packages/theme::theme.folder_is_not_writeable', ['name' => $themePath]),
                ];
            }

            $publishPath = $themePath . '/' . ($theme == Theme::getThemeName() ? Theme::getPublicThemeName() : $theme);

            if (! $this->files->isDirectory($publishPath)) {
                $this->files->makeDirectory($publishPath, 0755, true);
            }

            $configFile = theme_path($theme . '/config.php');
            $inheritTheme = $this->files->exists($configFile)
                ? ($this->files->getRequire($configFile)['inherit'] ?? null)
                : null;

            if ($inheritTheme) {
                $parentResourcePath = $this->getPath($inheritTheme, 'public');

                if ($this->files->isDirectory($parentResourcePath)) {
                    $this->files->copyDirectory($parentResourcePath, $publishPath);
                }

                $this->copyDirectoryWithoutOverwriting($resourcePath, $publishPath);
            } else {
                $this->files->copyDirectory($resourcePath, $publishPath);
            }

            $screenshot = $this->getPath($theme, 'screenshot.png');

            if ($this->files->exists($screenshot)) {
                $this->files->copy($screenshot, $publishPath . '/screenshot.png');
            }
        }

        if (! count($themes)) {
            return [
                'error' => true,
                'message' => 'No themes to publish assets.',
            ];
        }

        ThemeManager::clearCache();

        return [
            'error' => false,
            'message' => trans('packages/theme::theme.published_assets_success', ['themes' => implode(', ', $themes)]),
        ];
    }

    public function remove(string $theme): array
    {
        $validate = $this->validate($theme);

        if ($validate['error']) {
            return $validate;
        }

        if (Theme::getThemeName() === $theme) {
            return [
                'error' => true,
                'message' => trans('packages/theme::theme.cannot_remove_theme', ['name' => $theme]),
            ];
        }

        if (Theme::getInheritTheme() === $theme) {
            return [
                'error' => true,
                'message' => trans('packages/theme::theme.cannot_remove_inherit_theme', ['name' => $theme]),
            ];
        }

        $this->removeAssets($theme);

        $this->files->deleteDirectory($this->getPath($theme));
        Widget::query()
            ->where('theme', $theme)
            ->orWhere('theme', 'LIKE', $theme . '-%')
            ->delete();
        Setting::query()
            ->where('key', 'LIKE', ThemeOption::getOptionKey('%', theme: $theme))
            ->delete();

        event(new ThemeRemoveEvent($theme));

        ThemeManager::clearCache();

        return [
            'error' => false,
            'message' => trans('packages/theme::theme.theme_deleted', ['name' => $theme]),
        ];
    }

    public function removeAssets(string $theme): array
    {
        $validate = $this->validate($theme);

        if ($validate['error']) {
            return $validate;
        }

        $this->files->deleteDirectory(public_path('themes/' . $theme));

        return [
            'error' => false,
            'message' => trans('packages/theme::theme.removed_assets', ['name' => $theme]),
        ];
    }

    protected function getRelatedThemeNames(string $theme): array
    {
        return collect(BaseHelper::scanFolder(theme_path()))
            ->filter(fn (string $name) => $name !== $theme && Str::startsWith($name, $theme . '-'))
            ->values()
            ->all();
    }

    protected function getRelatedThemePrefixes(string $theme, string $keyPrefix = ''): array
    {
        return collect($this->getRelatedThemeNames($theme))
            ->map(fn (string $name) => $keyPrefix . $name . '-')
            ->all();
    }

    public function getThemeConfig(string $theme): array
    {
        $configFile = $this->getPath($theme, 'config.php');

        return $this->files->exists($configFile) ? $this->files->getRequire($configFile) : [];
    }
}
