<?php

namespace humhub\modules\wiki\models\forms;

use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\models\Content;
use humhub\modules\topic\models\Topic;
use humhub\modules\topic\permissions\AddTopic;
use humhub\modules\wiki\models\WikiPage;
use humhub\modules\wiki\models\WikiPageRevision;
use humhub\modules\wiki\Module;
use humhub\modules\wiki\permissions\AdministerPages;
use humhub\modules\wiki\widgets\WikiRichText;
use Yii;
use yii\base\Model;
use yii\db\Expression;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;

class PageEditForm extends Model
{
    /**
     * @var WikiPage
     */
    public $page;

    /**
     * @var ContentContainerActiveRecord
     */
    public $container;

    /**
     * @var WikiPageRevision
     */
    public $revision;

    /**
     * @var int
     */
    public $latestRevisionNumber;

    /**
     * @var bool
     */
    public $confirmOverwriting = 0;

    /**
     * @var bool
     */
    public $backOverwriting = 0;

    /**
     * @var bool
     */
    public $isPublic;

    /**
     * @var bool
     */
    public $hidden;

    /**
     * @var
     */
    public $topics = [];

    /**
     * @return array
     */
    public function rules()
    {
        return [
            ['topics', 'safe'],
            [['isPublic', 'confirmOverwriting', 'backOverwriting', 'hidden'], 'boolean'],
            ['latestRevisionNumber', 'validateLatestRevisionNumber'],
        ];
    }

    /**
     * Validate wiki page before saving in order to don't overwrite the latest revision by mistake
     *
     * @param string $attribute
     */
    public function validateLatestRevisionNumber($attribute)
    {
        if ($this->isNewPage()) {
            return;
        }

        if ($this->confirmOverwriting || $this->$attribute == $this->getLatestRevisionNumber()) {
            return;
        }

        if ($this->backOverwriting) {
            // Revert back to edit form with not saved page content after not confirmed overwrite
            $this->addError('backOverwriting', '');
            return;
        }

        // Mark the confirmation checkbox with red style and display it on edit form
        $this->addError('confirmOverwriting', '');
    }

    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        $labels = [
            'isPublic' => Yii::t('WikiModule.base', 'Is Public'),
        ];

        if (!$this->isNewPage()) {
            $labels['confirmOverwriting'] = Yii::t('WikiModule.base', 'Overwrite all changes made by :userName on :dateTime.', [
                ':dateTime' => Yii::$app->formatter->asDate($this->page->content->updated_at, 'medium') . ' - ' . Yii::$app->formatter->asTime($this->page->content->updated_at, 'short'),
                ':userName' => $this->page->content->updatedBy ? $this->page->content->updatedBy->displayName : '',
            ]);
        }

        return $labels;
    }

    /**
     * @return array
     */
    public function scenarios()
    {
        $editFields = ['latestRevisionNumber', 'confirmOverwriting', 'backOverwriting', 'hidden'];

        $scenarios = parent::scenarios();
        $scenarios[WikiPage::SCENARIO_CREATE] = ['topics'];
        $scenarios[WikiPage::SCENARIO_EDIT] =  $this->page->isOwner() ? array_merge(['topics'], $editFields) : $editFields;
        $scenarios[WikiPage::SCENARIO_ADMINISTER] = array_merge(['topics', 'isPublic'], $editFields);

        return $scenarios;
    }

    /**
     * @param int $id
     * @param string $title
     * @param int $categoryId
     * @return PageEditForm
     * @throws HttpException
     * @throws \yii\base\Exception
     * @throws \yii\base\InvalidConfigException
     * @throws \Throwable
     */
    public function forPage($id = null, $title = null, $categoryId = null)
    {
        $this->page = WikiPage::find()->contentContainer($this->container)->readable()->where(['wiki_page.id' => $id])->one();

        if (!$this->page && !$this->canCreatePage()) {
            throw new HttpException(403);
        }

        if ($this->page && !$this->page->canEditContent()) {
            throw new HttpException(403);
        }

        if (!$this->page) {
            $this->page = new WikiPage($this->container, ['title' => $title]);
            $this->setScenario(WikiPage::SCENARIO_CREATE);
        } else {
            $this->setScenario(WikiPage::SCENARIO_EDIT);
            $this->topics = $this->page->content->getTags(Topic::class)->all();
        }

        if ($this->canAdminister()) {
            $this->setScenario(WikiPage::SCENARIO_ADMINISTER) ;
        }

        $category = null;
        if ($categoryId) {
            $category = WikiPage::find()->contentContainer($this->container)->readable()->where(['wiki_page.id' => $categoryId])->one();
            if ($category) {
                $this->page->parent_page_id = $categoryId;
            }
        }

        $this->isPublic = $this->getPageVisibility($category);
        $this->revision = $this->page->createRevision();
        $this->latestRevisionNumber = $this->getLatestRevisionNumber();
        $this->hidden = $this->getPageHiddenStreamEntry();

        return $this;
    }

    private function getLatestRevisionNumber(): int
    {
        /* @var $latestRevision WikiPageRevision */
        $latestRevision = $this->page->latestRevision;
        return $latestRevision instanceof WikiPageRevision ? $latestRevision->revision : 0;
    }

    private function getPageVisibility($category = null)
    {
        if ($this->page->isNewRecord && $category) {
            return $category->content->visibility;
        }

        return $this->page->content->visibility;
    }

    private function getPageHiddenStreamEntry(): bool
    {
        if ($this->page->isNewRecord) {
            /** @var Module $module */
            $module = Yii::$app->getModule('wiki');
            return $module->getContentHiddenDefault($this->page->content->container);
        }

        return $this->page->content->hidden;
    }

    public function setScenario($value)
    {
        $this->page->setScenario($value);
        parent::setScenario($value); // TODO: Change the autogenerated stub
    }

    public function load($data, $formName = null)
    {
        return $this->page->load($data) | $this->revision->load($data) | parent::load($data);
    }

    /**
     * @return bool
     * @throws \Throwable
     */
    public function save()
    {
        if (!$this->validate()) {
            return false;
        }

        if ($this->isPublic !== null) {
            $this->page->content->visibility = $this->isPublic ? Content::VISIBILITY_PUBLIC : Content::VISIBILITY_PRIVATE;
        }

        if ($this->hidden !== null) {
            $this->page->content->hidden = $this->hidden;
        }

        return WikiPage::getDb()->transaction(function ($db) {
            if ($this->page->save()) {
                $this->revision->wiki_page_id = $this->page->id;

                if ($this->revision->save()) {
                    $this->page->fileManager->attach(Yii::$app->request->post('fileList'));

                    // This check is required because of a bug prior to HumHub v1.3.18 (https://github.com/humhub/humhub-modules-wiki/issues/103)
                    if ($this->page->content->container->can(AddTopic::class)) {
                        Topic::attach($this->page->content, $this->topics);
                    }

                    WikiRichText::postProcess($this->revision->content, $this->page);
                    return true;
                }
            }

            return false;
        });
    }

    /**
     * @param int|null $parentCategoryId Id of the parent category
     * @param int $level
     * @return array
     * @throws \yii\base\Exception
     */
    public function getCategoryList(?int $parentCategoryId = null, int $level = 0)
    {
        $categories = [];

        $query = WikiPage::find()->contentContainer($this->container);

        if ($parentCategoryId) {
            $query->andWhere(['wiki_page.parent_page_id' => $parentCategoryId]);
        } else {
            $query->andWhere(['IS', 'wiki_page.parent_page_id', new Expression('NULL')]);
        }

        if (!$this->isNewPage()) {
            $query->andWhere(['!=', 'wiki_page.id', $this->page->id]);
        }

        if (!$parentCategoryId) {
            $categories[] = Yii::t('WikiModule.base', 'None');
        }

        foreach ($query->each() as $category) {
            /* @var WikiPage $category */
            $categories[$category->id] = str_repeat('-', $level) . ' ' . $category->title;
            if ($subCategories = $this->getCategoryList($category->id, ++$level)) {
                $categories = ArrayHelper::merge($categories, $subCategories);
            }
            $level--;
        }

        return $categories;
    }

    public function getTitle()
    {
        return $this->isNewPage()
            ? Yii::t('WikiModule.base', 'Create new page')
            : Yii::t('WikiModule.base', 'Edit page');
    }

    public function isNewPage()
    {
        return $this->page->isNewRecord;
    }

    /**
     * @return bool can create new wiki site
     * @throws \yii\base\InvalidConfigException
     */
    public function canCreatePage()
    {
        return (new WikiPage($this->container))->content->canEdit();
    }

    /**
     * @return bool can manage wiki sites?
     * @throws \yii\base\InvalidConfigException
     */
    public function canAdminister()
    {
        return $this->container->permissionManager->can(AdministerPages::class);
    }

    public function isDisabledField($field)
    {
        return !($this->isEnabledFieldOnModel($this, $field) || $this->isEnabledFieldOnModel($this->page, $field));
    }

    /**
     * @param $model Model
     * @param $field
     * @return bool
     */
    private function isEnabledFieldOnModel($model, $field)
    {
        $scenarios = $model->scenarios();

        if (!isset($scenarios[$model->scenario])) {
            return false;
        }

        $allowed = $scenarios[$model->scenario];

        return in_array($field, $allowed);
    }
}
