<?php

declare(strict_types=1);

namespace pictpostpersonal\model;

use Exception;
use PDO;
use pictpostpersonal\PPPDateTime;
use pictpostpersonal\SQL;
use pictpostpersonal\SQLiteCRUD;

/**
 * 通知用のActivityを扱うクラス。
 * 記事へのlike付与も扱う。
 */
class Activity
{
    public const ACTIVITY_ACTION_LIKE = 'like';
    public const ACTIVITY_ACTION_VIEW = 'view';
    public const ACTIVITY_ACTION_MESSAGE = 'message';
    public const ACTIVITY_DELETE_SPAN = '-7 days';

    private SQLiteCRUD $sql_crud;

    /**
     * コンストラクタ
     *
     * @param PDO $db データベース接続オブジェクト
     */
    public function __construct(PDO $db)
    {
        $this->sql_crud = new SQLiteCRUD($db);
    }

    /**
     * メディア情報格納用のテーブルを生成する
     *
     * @return void
     */
    public static function initialize(SQLiteCRUD $sql_crud)
    {
        $sql_crud->table('activity')->createTable(
            [
                SQL::Id('id'),
                SQL::Integer('target'),
                SQL::DateTime('created_at'),
                SQL::Text('from_ip'),
                SQL::Text('action'),
                SQL::Bool('read', false)
            ]
        );
    }

    /**
     * すべてのアクティビティを既読にする
     *
     * @return void
     */
    public function markAllAsRead(): void
    {
        $this->sql_crud->execute('UPDATE activity SET read = 1 WHERE read = 0');
    }

    /**
     * 最新15件のアクティビティを取得する
     *
     * @param bool $isUnreadOnly true:未読のみ false:既読も含む
     * @return array アクティビティの一覧
     */
    public function findLatestActivities(bool $is_unread_only): array
    {
        $read_condition = $is_unread_only ? 'AND read <> 1' : '';

        $query = "
            SELECT 
                COUNT(*) AS count, 
                articles.like AS total, 
                articles.id, 
                articles.title,
                activity.action,
                activity.target,
                activity.created_at
            FROM activity 
            LEFT OUTER JOIN articles ON activity.target = articles.id 
            WHERE activity.action <> 'view' 
                {$read_condition} 
            GROUP BY activity.target, activity.action 
            ORDER BY activity.created_at DESC 
            LIMIT 15
        ";

        $result = $this->sql_crud->rawQuery($query);

        return $result;
    }

    /**
     * 同一IPから同一アクションが24時間以内にあったか確認する
     *
     * @param string $action アクションの種類。like, comment。
     * @param int    $articleId 対象記事のID。対象がない場合は-1。
     * @return bool
     */
    private function isDuplicateAction(string $action, $article_id): bool
    {
        $current_date_time = PPPDateTime::getCurrentDateTime();
        $before24Hours = date('Y-m-d H:i:s', strtotime('-24 hours', strtotime($current_date_time)));
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            $ip = 'unknown';
        }

        $conditions = [
            'target' => $article_id,
            'from_ip' => $ip,
            'action' => $action,
            'created_at' => $before24Hours
        ];

        $rows = $this->sql_crud->table('activity')
            ->where(
                'target = :target AND from_ip = :from_ip AND action = :action AND created_at > :created_at',
                $conditions
            )
            ->fetch(1);

        return count($rows) > 0;
    }

    /**
     * アクティビティを記録する
     *
     * @param string $action アクション内容。like, message等
     * @param int    $articleId 対象記事ID。対象がない場合は-1。
     * @param bool   $useTransaction トランザクションを使用するか。通常true。呼び出し元でトランザクションを使用している場合はfalse。
     * @return void
     *
     * @throws Exception データベース操作に失敗した場合
     */
    public function create(string $action, $article_id, bool $use_transaction = true): void
    {
        try {
            if ($use_transaction) {
                $this->sql_crud->begin();
            }

            $current_date_time = PPPDateTime::getCurrentDateTime();

            $data = [
                'target'     => $article_id,
                'action'     => $action,
                'from_ip'    => htmlspecialchars(strip_tags($_SERVER['REMOTE_ADDR'] ?? 'unknown')),
                'created_at' => $current_date_time
            ];

            $this->sql_crud->table('activity')->create($data);

            // 古いアクティビティを削除
            $before_delete_time = date('Y-m-d H:i:s', strtotime(self::ACTIVITY_DELETE_SPAN, strtotime($current_date_time)));
            $delete_query = 'DELETE FROM activity WHERE created_at < :beforeTime';
            $this->sql_crud->execute($delete_query, ['beforeTime' => $before_delete_time]);

            if ($use_transaction) {
                $this->sql_crud->commit();
            }
        } catch (Exception $e) {
            if ($use_transaction) {
                $this->sql_crud->rollback();
            }
            throw $e;
        }
    }

    public function addViewCount($article_id): void
    {
        // 24時間以内に同一操作がある場合は加算せず現在の数値を返す
        if ($this->isDuplicateAction(self::ACTIVITY_ACTION_VIEW, $article_id)) {
            return;
        }

        try {
            $this->sql_crud->begin();

            // 記事のいいね数を増加
            $update_query = 'UPDATE articles SET views = views + 1 WHERE id = :id';
            $this->sql_crud->execute($update_query, ['id' => $article_id]);

            // アクティビティ記録
            $this->create(self::ACTIVITY_ACTION_VIEW, $article_id, false);

            $this->sql_crud->commit();

            return;
        } catch (Exception $e) {
            $this->sql_crud->rollback();
            throw $e;
        }
    }

    /**
     * いいね増加処理
     *
     * @param int $articleId 記事ID
     * @return int 増加後のいいね数
     *
     * @throws Exception データベース操作に失敗した場合
     */
    public function addLikeCount($article_id): int
    {
        // 24時間以内に同一操作がある場合は加算せず現在の数値を返す
        if ($this->isDuplicateAction(self::ACTIVITY_ACTION_LIKE, $article_id)) {
            return $this->getLikeCount($article_id);
        }

        try {
            $this->sql_crud->begin();

            // 記事のいいね数を増加
            $update_query = 'UPDATE articles SET like = like + 1 WHERE id = :id';
            $this->sql_crud->execute($update_query, ['id' => $article_id]);

            // アクティビティ記録
            $this->create(self::ACTIVITY_ACTION_LIKE, $article_id, false);

            $this->sql_crud->commit();

            return $this->getLikeCount($article_id);
        } catch (Exception $e) {
            $this->sql_crud->rollback();
            throw $e;
        }
    }

    /**
     * いいね数を取得する
     *
     * @param int $articleId 記事ID
     * @return int 現在のいいね数
     */
    public function getLikeCount($article_id): int
    {
        $rows = $this->sql_crud->table('articles')
            ->where('id = :id', ['id' => $article_id])
            ->fetch();

        return (int)($rows[0]['like'] ?? 0);
    }

    public function getDayLikeCount($target_date = null): int
    {
        if ($target_date == null) {
            $target_date = PPPDateTime::getCurrentDateTime();
        }
        $sdt = new \DateTime($target_date);
        $sdt->setTime(0, 0, 0);

        $edt = new \DateTime($target_date);
        $edt->modify('+1 days');
        $edt->setTime(0, 0, 0);


        $start = $sdt->format('Y-m-d H:i:s');
        $end = $edt->format('Y-m-d H:i:s');
        $rows = $this->sql_crud->table('activity')
            ->select('count(*) as total_count')
            ->where("created_at >= :start_at and created_at < :end_at and action = 'like'", ['start_at' => $start, 'end_at' => $end])
            ->fetch();

        return (int)($rows[0]['total_count'] ?? 0);
    }
    public function getWeekLikeCount($now_date = null): int
    {
        if ($now_date == null) {
            $now_date = PPPDateTime::getCurrentDateTime();
        }
        $dt = new \DateTime($now_date);
        $dt->modify('-7 days');
        $dt->setTime(0, 0, 0);
        $start = $dt->format('Y-m-d H:i:s');
        $rows = $this->sql_crud->table('activity')
            ->select('count(*) as total_count')
            ->where("created_at >= :created_at and action = 'like'", ['created_at' => $start])
            ->fetch();

        return (int)($rows[0]['total_count'] ?? 0);
    }
}
