<?php

namespace pictpostpersonal\controller\api;

use pictpostpersonal\controller\ControllerBase;

use Exception;
use pictpostpersonal\Environment;
use pictpostpersonal\RssFeed;
use pictpostpersonal\Result;
use pictpostpersonal\Request;
use pictpostpersonal\model\Activity;
use pictpostpersonal\model\Article;
use pictpostpersonal\model\Media;
use pictpostpersonal\model\Tag;
use pictpostpersonal\Utility;

/**
 * 記事操作にかかわるAPIを提供するクラス
 */
class ArticleApi extends ControllerBase
{

    function varidate(Request $req): ?string
    {
        if (isset($req->args_raw['created_at']) == false || $req->args_raw['created_at']  == '' || !Utility::isValidDate($req->args_raw['created_at'])) {
            return "作成日時が正しくありません";
        }

        $altUrl = $req->getArgs('altUrl', '');
        if ($altUrl) {
            if (strpos($altUrl, 'api/') === 0) {
                return "api/から始まる固定URLは指定できません";
            }
            if ($altUrl == 'log') {
                return "logは固定URLに指定できません";
            }
            if (preg_match('/log/[0-9]+/', $altUrl)) {
                return "log/数字 の形式は固定URLに指定できません";
            }
            if (preg_match('/(\./)/', $altUrl)) {
                return "URLに使用できない文字が含まれています";
            }
        }
        return null;
    }

    /**
     * 記事を作成する
     *
     * @param Request $req
     * 
     * @return result
     * 
     */
    function create(Request $req): Result
    {
        if ($req->hasUploadErrors()) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"アップロードされたファイルが大きすぎるか、不正です\"}";
            return $result;
        }
        $checkMessage = $this->varidate($req);
        if ($checkMessage !== null) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$checkMessage}\"}";
            return $result;
        }

        unset($req->args_raw['id']);
        return $this->write($req);
    }

    /**
     * 記事を更新する
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    function update(Request $req): Result
    {
        if ($req->hasUploadErrors()) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"アップロードされたファイルが大きすぎるか、不正です\"}";
            return $result;
        }

        if ($req->args_raw['id'] == '') {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"IDが指定されていません\"}";
            return $result;
        }

        $checkMessage = $this->varidate($req);
        if ($checkMessage !== null) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$checkMessage}\"}";
            return $result;
        }

        return $this->write($req);
    }


    /**
     * ゲストモード用 記事作成処理
     *
     * @param Request $req
     *
     * @return Result
     *
     */
    function guest_create(Request $req): Result
    {
        unset($req['id']);
        return $this->guest_write($req);
    }


    /**
     * ゲストモード用 記事更新処理
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    function guest_update(Request $req): Result
    {
        $id = $req['id'];
        if ($req['id'] == '') {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"IDが指定されていません\"}";
            return $result;
        }

        $key = $req['delete_key'];
        if ($key == '') {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"削除キーが指定されていません\"}";
            return $result;
        }

        $article = new Article($this->db, $this->setting);
        if (!$article->validateDeleteKey($id, $key)) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"削除キーが不正です\"}";
            return $result;
        }

        $checkMessage = $this->varidate($req);
        if ($checkMessage !== null) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$checkMessage}\"}";
            return $result;
        }

        return $this->guest_create($req); //作成処理と共用
    }


    /**
     * 記事 / 画像の書き込みを実行する処理本体
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    private function write(Request $req): Result
    {
        //IPアドレスを記録
        $req->args_raw['from_ip']  = $_SERVER['REMOTE_ADDR'] ?? 'unknonw';

        try {
            $args = $req->args_raw;

            //validate
            if (isset($args['created_at']) == false || $args['created_at']  == '') {
                $result = new Result();
                $result->mime_type = 'application/json';
                $result->output = "{\"status\":\"error\", \"message\":\"{データが不正です}\"}";
                return $result;
            }

            //データ調整
            //スタイル = 99は固定ページ
            if ($args['style'] == 99) {
                $args['type'] = 1;
            } else if ($args['style']  != 99 && $args['type'] == '1') {
                $args['type'] = 0;
            }

            //添付ファイル検証
            $files = $req->getFiles();
            $this->validateFiles($files);

            //WAF対策
            if (isset($args['contents'])) {
                $args['contents'] = base64_decode($args['contents']);
            }

            //タイトルが空の場合作成日時をタイトルにする
            if (isset($args['title']) === false || $args['title'] == '') {
                $args['title'] = $args['created_at'];
            }

            //デモ動作の場合 ゲストモードと同様のフィルタをかける
            if (Environment::isDemoMode()) {
                $args = $this->guestFilter($args, false);
            }


            //サムネイルタイプ
            $thumb = $args['thumbnail'] ?? 0;
            switch ($thumb) {
                case '': //自動サムネイル
                    $args['thumbnail_type'] = 0;
                    break;
                case '@user_thumbnail': //全画像を表示
                    $args['thumbnail_type'] = 1;
                    break;
                case '@all': //1枚目がサムネイル
                    $args['thumbnail_type'] = 2;
                    break;
                case '@text': //サムネイルなし
                    $args['thumbnail_type'] = 4;
                    break;
                default:
                    $args['thumbnail_type'] = 3;
                    break;
            }

            $article = new Article($this->db, $this->setting);
            if (isset($args['id']) && $args['id'] != '') {

                //更新の場合 Like数とView数はアップデートしない
                unset($args["views"]);
                unset($args["likes"]);

                $id = $article->update($args, $files);
            } else {
                $id = $article->create($args, $files);
            }
            $article_data = $article->GetById($id, true);
            $article_data['url'] = Environment::getLogUrl($article_data);
            $result_data = [
                'status' => 'success',
                'id' => $id,
                'message' => 'success',
                'article_data' => $article_data
            ];

            //タグクラウドを更新する
            $tags = new Tag($this->db);
            $tags->createTagCloud();

            //RSSを更新する
            $rss = new RssFeed($this->setting);
            $rss->createFeed($article);

            //Portal更新する
            if ($this->setting->getValue('use_portal_feed', false)) {
                try {
                    $rss->sendRequest($article);
                } catch (Exception $ex) {
                }
            }

            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = json_encode($result_data);
            return $result;
        } catch (Exception $ex) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$ex->getMessage()}\"}";
            return $result;
        }
    }

    /**
     * ゲスト用書き込み処理
     *  ・削除キーの書き込みあり
     *  ・HTMLタグ使用不可
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    private function guest_write(Request $req): Result
    {
        $req->args_raw['from_ip']  = $_SERVER['REMOTE_ADDR'] ?? 'unknonw';
        try {
            $args = $req->args_raw;

            //validate
            if ($args['created_at'] == '') {
                $result = new Result();
                $result->mime_type = 'application/json';
                $result->output = "{\"status\":\"error\", \"message\":\"{データが不正です}\"}";
                return $result;
            }

            //添付ファイル検証
            $files = $req->getFiles();
            $this->validateFiles($files);

            //WAF対策
            if (isset($args['contents'])) {
                $args['contents'] = base64_decode($args['contents']);
            }

            //ゲストモード用フィルタを適用する
            $args = $this->guestFilter($args, true);

            $article = new Article($this->db, $this->setting);
            if (isset($args['id']) && $args['id'] != '') {
                $id = $article->update($args, $files);
            } else {
                $id = $article->create($args, $files);
            }

            $article_data = $article->GetById($id, true);
            $article_data['url'] = Environment::getLogUrl($article_data);
            $result_data = [
                'status' => 'success',
                'id' => $id,
                'message' => 'success',
                'article_data' => $article_data
            ];

            //タグクラウドを更新する
            $tags = new Tag($this->db);
            $tags->createTagCloud();

            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = json_encode($result_data);
            return $result;
        } catch (Exception $ex) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$ex->getMessage()}\"}";
            return $result;
        }
    }

    /**
     * ゲスト投稿用のデータフィルタリング処理
     * ・すべてのHTMLを無効化する
     * ・すべての特殊記述を無効化する
     * 
     * @param array $args
     * 
     * @return array
     * 
     */
    private function guestFilter(array $args, bool $remove_special_tag): array
    {
        foreach ($args as $index => $arg) {
            if (is_string($arg)) {
                $args[$index] = s($arg);
                if ($remove_special_tag) {
                    $args[$index] = str_replace('{', '&#060;', $args[$index]);
                    $args[$index] = str_replace('}', '&#062;', $args[$index]);
                }
            }
        }
        return $args;
    }

    /**
     * 記事を削除する
     *
     * @param Request $req
     *
     * @return Result
     *
     */
    function delete(Request $req): Result
    {
        try {
            $id = $req->getArgs('id', -1, Request::FILTER_INT);
            if ($id == -1) {

                $result = new Result();
                $result->mime_type = 'application/json';
                $result->output = "{\"status\":\"error\", \"message\":\"{IDが不正です}\"}";
                return $result;
            }

            $article = new Article($this->db, $this->setting);
            $article->deleteByIDList([$id]);

            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"success\", \"id\":\"$id\", \"message\":\"success\"}";
            return $result;
        } catch (Exception $ex) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$ex->getMessage()}\"}";
            return $result;
        }
    }

    /**
     * 記事の添付画像を投稿する
     *
     * @param Request $req
     *
     * @return Result
     *
     */
    function uploadMedia(Request $req): Result
    {
        try {
            $id = $req->getArgs('id', -1, Request::FILTER_INT);
            if ($id == -1) {
                $result = new Result();
                $result->mime_type = 'application/json';
                $result->output = "{\"status\":\"error\", \"message\":\"{IDが不正です}\"}";
                return $result;
            }

            //添付ファイル検証
            $files = $req->getFiles();
            $this->validateFiles($files);

            $article = new Article($this->db, $this->setting);
            $article->addMedia($id, $req->args_raw, $files);

            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"success\", \"id\":\"$id\", \"message\":\"success\"}";
            return $result;
        } catch (Exception $ex) {
            $result = new Result();
            $result->mime_type = 'application/json';
            $result->output = "{\"status\":\"error\", \"message\":\"{$ex->getMessage()}\"}";
            return $result;
        }
    }

    /**
     * メディアIDからURLを取得する
     *
     * @param Request $req
     * 
     * @return string
     * 
     */
    function getMediaUrl(Request $req): string
    {
        $media_id = $req->getArgs('media_id', null, Request::FILTER_INT);

        $media = new Media($this->db, $this->setting);
        $data = $media->getByMediaId($media_id);
        return  SITE_URL . "/images/{$data['sub_dir']}{$data['file_name']}";
    }


    /**
     * いいね数を増加させる
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    function addLikeCount(Request $req): Result
    {
        $id = $req->getArgs('id', '');
        if ($id == '') {
            return new Result(400, "{\"status\":\"error\", \"message\":\"IDが指定されていません\"}");
        }

        $model = new Activity($this->db);
        $likes = $model->addLikeCount($id);

        return new Result(200, "{\"status\":\"success\", \"message\":\"OK\", \"like\":\"$likes\"}");
    }

    /**
     * いいね数を取得する
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    function getLikeCount(Request $req): Result
    {
        $id = $req->getArgs('id', '');
        if ($id == '') {
            return new Result(400, "{\"status\":\"error\", \"message\":\"IDが指定されていません\"}");
        }

        $model = new Activity($this->db);
        $likes = $model->getLikeCount($id);

        return new Result(200, "{\"status\":\"success\", \"message\":\"OK\", \"like\":\"$likes\"}");
    }

    /**
     * 今週のいいね数を取得する
     *
     * @param Request $req
     * 
     * @return Result
     * 
     */
    function getWeekLikes(Request $req): Result
    {
        $model = new Activity($this->db);
        $likes = $model->getWeekLikeCount();
        return new Result(200, "{\"status\":\"success\", \"message\":\"OK\", \"like\":\"$likes\"}");
    }

    /**
     * アップロードされたファイルの検証を行う
     *
     * @param File[] $files
     * 
     * @return void
     * 
     * @throws Exception
     */
    private function validateFiles(array $files): void
    {
        foreach ($files as $files_list) {
            foreach ($files_list as $file) {
                if (!$file->checkAllowedExtension(['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'ogg'])) {
                    throw new Exception('サポートされていないファイル形式のファイルがアップロードされました');
                }
                if ($file->error != UPLOAD_ERR_NO_FILE && $file->error != UPLOAD_ERR_OK) {
                    throw new Exception('アップロードされたファイルが大きすぎるか、不正です');
                }
                if ($file->getMediaType() == 'unknown') {
                    throw new Exception('サポートされていないファイル形式のファイルがアップロードされました');
                }
            }
        }
    }
}
