<?php

namespace Kylin\Admin\Extend;

use Kylin\Admin\Admin;
use Kylin\Admin\Exception\RuntimeException;
use Kylin\Admin\Support\ComposerProperty;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
use Symfony\Component\Console\Output\NullOutput;

abstract class ServiceProvider extends LaravelServiceProvider
{
    use CanImportMenu;

    const TYPE_THEME = 'theme';

    /**
     * @var ComposerProperty
     */
    public $composerProperty;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $packageName;

    /**
     * @var string
     */
    protected $type;

    /**
     * @var string
     */
    protected $path;

    /**
     * @var array
     */
    protected $js = [];

    /**
     * @var array
     */
    protected $css = [];

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    public $output;

    /**
     * @var array
     */
    protected $config;

    /**
     * @var array
     */
    protected $middleware = [];

    /**
     * @var array
     */
    protected $exceptRoutes = [];

    public function __construct($app)
    {
        parent::__construct($app);

        $this->output = new NullOutput();
    }

    /**
     * {@inheritdoc}
     */
    final public function boot()
    {
        $this->autoRegister();

        if ($this->disabled()) {
            return;
        }

        $this->init();
    }

    /**
     * 初始化操作.
     *
     * @return void
     */
    public function init()
    {
        if ($views = $this->getViewPath()) {
            $this->loadViewsFrom($views, $this->getName());
        }

        if ($lang = $this->getLangPath()) {
            $this->loadTranslationsFrom($lang, $this->getName());
        }

        if ($routes = $this->getRoutes()) {
            $this->registerRoutes($routes);
        }

        if ($this->middleware()) {
            $this->addMiddleware();
        }

        if ($this->exceptRoutes) {
            $this->addExceptRoutes();
        }

        $this->aliasAssets();
    }

    /**
     * 自动注册扩展.
     */
    protected function autoRegister()
    {
        if ($this->getName()) {
            return;
        }

        Admin::extension()->addExtension($this);
    }

    /**
     * 获取扩展名称.
     *
     * @return string|void
     */
    public function getName()
    {
        return $this->name ?: ($this->name = str_replace('/', '.', $this->getPackageName()));
    }

    /**
     * 获取扩展别名.
     *
     * @return string
     */
    public function getAlias()
    {
        if (!$this->composerProperty) {
            return;
        }

        return $this->composerProperty->alias;
    }

    /**
     * 获取包名.
     *
     * @return string|void
     */
    public function getPackageName()
    {
        if (!$this->packageName) {
            if (!$this->composerProperty) {
                return;
            }

            $this->packageName = $this->composerProperty->name;
        }

        return $this->packageName;
    }

    /**
     * 获取插件类型.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * 获取当前已安装版本.
     *
     * @return string
     */
    public function getVersion()
    {
        return Admin::extension()->versionManager()->getCurrentVersion($this);
    }

    /**
     * 获取当前最新版本.
     *
     * @return string
     */
    public function getLatestVersion()
    {
        return Admin::extension()->versionManager()->getFileVersions($this);
    }

    /**
     * 获取当前本地最新版本.
     *
     * @return string
     */
    public function getLocalLatestVersion()
    {
        return last(
            array_keys(Admin::extension()->versionManager()->getFileVersions($this))
        );
    }

    /**
     * 获取扩展包路径.
     *
     * @param string $path
     * @return string
     *
     * @throws \ReflectionException
     */
    public function path(?string $path = null)
    {
        if (!$this->path) {
            $this->path = realpath(dirname((new \ReflectionClass(static::class))->getFileName()) . '/..');

            if (!is_dir($this->path)) {
                throw new RuntimeException("The {$this->path} is not a directory.");
            }
        }

        $path = ltrim($path, '/');

        return $path ? $this->path . '/' . $path : $this->path;
    }

    /**
     * 获取logo路径.
     *
     * @return string
     *
     * @throws \ReflectionException
     */
    public function getLogoPath()
    {
        return $this->path('logo.png');
    }

    /**
     * @return string
     */
    public function getLogoBase64()
    {
        try {
            $logo = $this->getLogoPath();

            if (is_file($logo) && $file = fopen($logo, 'rb', 0)) {
                $content = fread($file, filesize($logo));
                fclose($file);
                $base64 = chunk_split(base64_encode($content));

                return 'data:image/png;base64,' . $base64;
            }
        } catch (\ReflectionException $e) {
        }
    }

    /**
     * 判断扩展是否启用.
     *
     * @return bool
     */
    public function enabled()
    {
        return Admin::extension()->enabled($this->getName());
    }

    /**
     * 判断扩展是否禁用.
     *
     * @return bool
     */
    public function disabled()
    {
        return !$this->enabled();
    }

    /**
     * 获取或保存配置.
     *
     * @param string $key
     * @param null $default
     * @return mixed
     */
    public function config($key = null, $default = null)
    {
        if ($this->config === null) {
            $this->initConfig();
        }

        if (is_array($key)) {
            $this->saveConfig($key);

            return;
        }

        if ($key === null) {
            return $this->config;
        }

        return Arr::get($this->config, $key, $default);
    }

    /**
     * 保存配置.
     *
     * @param array $config
     */
    public function saveConfig(array $config)
    {
        $this->config = array_merge($this->config, $config);

        Admin::setting()->save([$this->getConfigKey() => $this->serializeConfig($this->config)]);
    }

    /**
     * 初始化配置.
     */
    protected function initConfig()
    {
        $this->config = Admin::setting()->get($this->getConfigKey());
        $this->config = $this->config ? $this->unserializeConfig($this->config) : [];
    }

    /**
     * 更新扩展.
     *
     * @param string $currentVersion
     * @param string $stopOnVersion
     *
     * @throws \Exception
     */
    public function update($currentVersion, $stopOnVersion)
    {
        $this->refreshMenu();
    }

    /**
     * 卸载扩展.
     */
    public function uninstall()
    {
        $this->flushMenu();
    }

    /**
     * 发布静态资源.
     */
    public function publishable()
    {
        if ($assets = $this->getAssetPath()) {
            $this->publishes([
                $assets => $this->getPublishsPath(),
            ], $this->getName());
        }
    }

    /**
     * 获取资源发布路径.
     *
     * @return string
     */
    protected function getPublishsPath()
    {
        return public_path(
            Admin::asset()->getRealPath('@extension/' . str_replace('.', '/', $this->getName()))
        );
    }

    /**
     * 注册路由.
     *
     * @param $callback
     */
    public function registerRoutes($callback)
    {
        Admin::app()->routes(function ($router) use ($callback) {
            $router->group([
                'prefix' => config('admin.route.prefix'),
                'middleware' => config('admin.route.middleware'),
            ], $callback);
        });
    }

    /**
     * 获取中间件.
     *
     * @return array
     */
    protected function middleware()
    {
        return $this->middleware;
    }

    /**
     * 注册中间件.
     */
    protected function addMiddleware()
    {
        $adminMiddleware = (array)config('admin.route.middleware');
        $middleware = $this->middleware();

        $before = $middleware['before'] ?? [];
        $middle = $middleware['middle'] ?? [];
        $after = $middleware['after'] ?? [];

        $this->mixMiddleware($middle);

        config([
            'admin.route.middleware' => array_merge((array)$before, $adminMiddleware, (array)$after),
        ]);
    }

    protected function mixMiddleware(array $middle)
    {
        Admin::mixMiddlewareGroup($middle);
    }

    /**
     * 配置需要跳过权限认证和登录认证的路由.
     */
    protected function addExceptRoutes()
    {
        if (!empty($this->exceptRoutes['permission'])) {
            Admin::context()->merge('permission.except', (array)$this->exceptRoutes['permission']);
        }

        if (!empty($this->exceptRoutes['auth'])) {
            Admin::context()->merge('auth.except', (array)$this->exceptRoutes['auth']);
        }
    }

    /**
     * 获取静态资源路径.
     *
     * @return string
     */
    final public function getAssetPath()
    {
        return $this->path('resources/assets');
    }

    /**
     * 获取视图路径.
     *
     * @return string
     */
    final public function getViewPath()
    {
        return $this->path('resources/views');
    }

    /**
     * 获取语言包路径.
     *
     * @return string
     */
    final public function getLangPath()
    {
        return $this->path('resources/lang');
    }

    /**
     * 获取路由地址.
     *
     * @return string
     *
     * @throws \ReflectionException
     */
    final public function getRoutes()
    {
        $path = $this->path('src/Http/routes.php');

        return is_file($path) ? $path : null;
    }

    /**
     * @param ComposerProperty $composerProperty
     * @return $this
     */
    public function withComposerProperty(ComposerProperty $composerProperty)
    {
        $this->composerProperty = $composerProperty;

        return $this;
    }

    /**
     * 获取或保存配置.
     *
     * @param string $key
     * @param string $value
     * @return mixed
     */
    public static function setting($key = null, $value = null)
    {
        $extension = static::instance();

        if ($extension && $extension instanceof ServiceProvider) {
            return $extension->config($key, $value);
        }
    }

    /**
     * 翻译.
     *
     * @param string $key
     * @param array $replace
     * @param null $locale
     * @return array|string|null
     */
    public static function trans($key, $replace = [], $locale = null)
    {
        return trans(static::instance()->getName() . '::' . $key, $replace, $locale);
    }

    /**
     * 获取自身实例.
     *
     * @return $this
     */
    public static function instance()
    {
        return app(static::class);
    }

    /**
     * 注册别名.
     */
    protected function aliasAssets()
    {
        $asset = Admin::asset();

        // 注册静态资源路径别名
        $asset->alias($this->getName() . '.path', '@extension/' . $this->getPackageName());

        if ($this->js || $this->css) {
            $asset->alias($this->getName(), [
                'js' => $this->formatAssetFiles($this->js),
                'css' => $this->formatAssetFiles($this->css),
            ]);
        }
    }

    /**
     * @param string|array $files
     * @return mixed
     */
    protected function formatAssetFiles($files)
    {
        if (is_array($files)) {
            return array_map([$this, 'formatAssetFiles'], $files);
        }

        if (URL::isValidUrl($files)) {
            return $files;
        }

        return '@' . $this->getName() . '.path/' . trim($files, '/');
    }

    /**
     * 配置key.
     *
     * @return mixed
     */
    protected function getConfigKey()
    {
        return str_replace('.', ':', $this->getName());
    }

    /**
     * @param $config
     * @return false|string
     */
    protected function serializeConfig($config)
    {
        return json_encode($config);
    }

    /**
     * @param $config
     * @return array
     */
    protected function unserializeConfig($config)
    {
        return json_decode($config, true);
    }
}
