<?php

if ( ! defined( 'ABSPATH' ) ) {

    exit;

}



/**

 * ライセンス・利用制限管理

 *

 * Freemius を介して Free / Pro プランを判定し、Free プランの場合は

 * 暦月単位で投稿数を制限する。

 *

 * Pro 機能（現在）:

 *   - 投稿し損ねた予定の処理(後処理)

 *   - SEO 最適化(全項目)

 *

 * Free プランの制限:

 *   - 月10投稿まで(Quick Post + cron 投稿の合計)

 *

 * Freemius SDK が未配置の場合は常に Free 扱いで動作する。

 * これにより SDK 統合前でも開発・テストが可能。

 *

 * 月跨ぎは get_counter() で自動リセット(暦月の 1 日 00:00 を境界)。

 */

class AIAP_License {



    /**

     * 投稿カウンタ option キー

     */

    const OPTION_COUNTER = 'aiap_post_counter';



    /**

     * Free プランの月間投稿上限

     */

    const FREE_MONTHLY_LIMIT = 10;



    /**

     * Pro 機能識別子

     */

    const FEATURE_SCHEDULE_RECOVERY = 'schedule_recovery';

    const FEATURE_SEO               = 'seo';



    // ====================================================================

    // ライセンス判定

    // ====================================================================



    /**

     * Pro 機能が利用可能か?(有料 or トライアル中)

     *

     * Freemius が初期化済みでない場合は常に false (= Free 扱い)。

     */

    public static function is_pro() {

        if ( function_exists( 'aiap_fs' ) ) {

            $fs = aiap_fs();

            // 有料プラン or トライアル中なら Pro 機能解禁

            if ( method_exists( $fs, 'can_use_premium_code__premium_only' ) && $fs->can_use_premium_code__premium_only() ) {

                return true;

            }

            if ( method_exists( $fs, 'is_paying' ) && $fs->is_paying() ) {

                return true;

            }

            if ( method_exists( $fs, 'is_trial' ) && $fs->is_trial() ) {

                return true;

            }

        }

        return false;

    }



    /**

     * トライアル中か?

     */

    public static function is_trial() {

        if ( function_exists( 'aiap_fs' ) && method_exists( aiap_fs(), 'is_trial' ) ) {

            return aiap_fs()->is_trial();

        }

        return false;

    }



    /**

     * トライアル残り日数(整数。トライアルでない場合は 0)

     */

    public static function trial_days_remaining() {

        if ( ! self::is_trial() ) {

            return 0;

        }

        $fs = aiap_fs();

        if ( ! method_exists( $fs, 'get_trial_plan' ) ) {

            return 0;

        }

        // Freemius は trial_ends を秒単位で持つ

        if ( method_exists( $fs, 'get_site' ) ) {

            $site = $fs->get_site();

            if ( $site && ! empty( $site->trial_ends ) ) {

                $ends   = strtotime( $site->trial_ends );

                $remain = $ends - time();

                return max( 0, (int) ceil( $remain / DAY_IN_SECONDS ) );

            }

        }

        return 0;

    }



    /**

     * 個別 Pro 機能の解禁判定

     *

     * 現状は is_pro() に集約しているが、将来的にプラン階層で分けられるよう

     * フィーチャ単位の API を提供しておく。

     */

    public static function has_feature( $feature ) {

        unset( $feature );

        return self::is_pro();

    }



    /**

     * アップグレード URL

     */

    public static function upgrade_url() {

        if ( function_exists( 'aiap_fs' ) && method_exists( aiap_fs(), 'get_upgrade_url' ) ) {

            return aiap_fs()->get_upgrade_url();

        }

        return admin_url( 'admin.php?page=ai-auto-poster' );

    }



    /**

     * トライアル開始 URL

     */

    public static function trial_url() {

        if ( function_exists( 'aiap_fs' ) && method_exists( aiap_fs(), 'get_trial_url' ) ) {

            return aiap_fs()->get_trial_url();

        }

        return self::upgrade_url();

    }



    /**

     * Freemius が利用可能か(SDK + キーが設定されているか)

     */

    public static function is_freemius_active() {

        return function_exists( 'aiap_fs' );

    }



    // ====================================================================

    // 月次投稿カウンタ

    // ====================================================================



    /**

     * 今月の投稿数(Free/Pro 問わず累積されるが、Pro では未使用)

     */

    public static function monthly_post_count() {

        $counter = self::get_counter();

        return (int) $counter['count'];

    }



    /**

     * 今月の上限(Pro なら PHP_INT_MAX)

     */

    public static function monthly_limit() {

        return self::is_pro() ? PHP_INT_MAX : self::FREE_MONTHLY_LIMIT;

    }



    /**

     * 今月の残り投稿可能数

     */

    public static function remaining_posts() {

        if ( self::is_pro() ) {

            return PHP_INT_MAX;

        }

        return max( 0, self::FREE_MONTHLY_LIMIT - self::monthly_post_count() );

    }



    /**

     * もう投稿できるか?

     */

    public static function can_post_more() {

        return self::remaining_posts() > 0;

    }



    /**

     * 共通 WP_Query 引数 (生成投稿の集計用)。

     *

     * Bug fix / Dedup: daily_post_counts と today_post_breakdown で重複していた

     * post_type / post_status / posts_per_page / fields / no_found_rows / date_query を

     * 1 箇所に集約する。post_status の値変更時の同期漏れリスクを排除。

     *

     * @param DateTimeImmutable $start

     * @param DateTimeImmutable $end

     * @return array WP_Query 引数の基底テンプレート

     */

    private static function build_generated_posts_query_args( DateTimeImmutable $start, DateTimeImmutable $end ) {

        return array(

            'post_type'      => 'post',

            // increment_post_count() は post_status を問わず加算する (= aiap_post_counter)。

            // ここの集計と整合させるため、draft/pending/private も含める。

            'post_status'    => array( 'publish', 'draft', 'pending', 'future', 'private' ),

            'posts_per_page' => -1,

            'fields'         => 'ids',

            'no_found_rows'  => true,

            'date_query'     => array(

                array(

                    'after'     => $start->format( 'Y-m-d H:i:s' ),

                    'before'    => $end->format( 'Y-m-d H:i:s' ),

                    'inclusive' => true,

                ),

            ),

        );

    }



    /**

     * ダッシュボード向けヘルパー (Phase 1).

     * 直近 N 日間の日別投稿数を返す。

     * 形式: array( 'YYYY-MM-DD' => int, ... ) - 古い日付から新しい日付の順

     */

    public static function daily_post_counts( $days = 7 ) {

        $days = max( 1, min( 90, (int) $days ) );

        $tz   = wp_timezone();



        // 結果初期化 (古い→新しい)

        $result = array();

        for ( $i = $days - 1; $i >= 0; $i-- ) {

            $d = ( new DateTimeImmutable( 'now', $tz ) )->modify( "-{$i} day" );

            $result[ $d->format( 'Y-m-d' ) ] = 0;

        }



        // 期間の start/end (タイムゾーン考慮)

        $start = ( new DateTimeImmutable( 'now', $tz ) )->modify( '-' . ( $days - 1 ) . ' day' )->setTime( 0, 0, 0 );

        $end   = ( new DateTimeImmutable( 'now', $tz ) )->setTime( 23, 59, 59 );



        $args = self::build_generated_posts_query_args( $start, $end );

        $args['meta_key']   = '_aiap_generated';

        $args['meta_value'] = '1';

        $q = new WP_Query( $args );

        if ( ! empty( $q->posts ) ) {

            foreach ( $q->posts as $pid ) {

                $post_date = get_post_field( 'post_date', $pid );

                if ( ! $post_date ) continue;

                $key = substr( $post_date, 0, 10 );

                if ( isset( $result[ $key ] ) ) {

                    $result[ $key ]++;

                }

            }

        }

        return $result;

    }



    /**

     * 本日の投稿数を自動 / 手動に分けて取得

     *

     * 区別の根拠:

     *   - 全 AI 生成 post には _aiap_generated=1 が付く

     *   - クイック投稿 (手動) には追加で _aiap_manual=1 が付く (admin.php の ajax_run_now_step 経由)

     *   - cron 経由 (自動) には _aiap_manual は付かない

     *

     * @return array {

     *   @type int auto    今日 0:00〜23:59 に作成された自動投稿の件数

     *   @type int manual  同 手動投稿の件数

     * }

     */

    public static function today_post_breakdown() {

        $tz    = wp_timezone();

        $start = ( new DateTimeImmutable( 'now', $tz ) )->setTime( 0, 0, 0 );

        $end   = ( new DateTimeImmutable( 'now', $tz ) )->setTime( 23, 59, 59 );



        $base = self::build_generated_posts_query_args( $start, $end );



        // 手動: _aiap_manual=1

        $manual_q = new WP_Query( array_merge( $base, array(

            'meta_query' => array(

                array( 'key' => '_aiap_manual', 'value' => '1' ),

            ),

        ) ) );

        $manual_count = is_array( $manual_q->posts ) ? count( $manual_q->posts ) : 0;



        // 自動: _aiap_generated=1 かつ _aiap_manual が存在しない

        $auto_q = new WP_Query( array_merge( $base, array(

            'meta_query' => array(

                'relation' => 'AND',

                array( 'key' => '_aiap_generated', 'value' => '1' ),

                array( 'key' => '_aiap_manual',    'compare' => 'NOT EXISTS' ),

            ),

        ) ) );

        $auto_count = is_array( $auto_q->posts ) ? count( $auto_q->posts ) : 0;



        return array(

            'auto'   => (int) $auto_count,

            'manual' => (int) $manual_count,

        );

    }



    /**

     * 投稿成功時にカウンタを 1 増やす

     *

     * Pro でもカウンタは記録する(将来分析用 + 解約時に Free に戻った際の

     * 連続性のため)。Free 制限は can_post_more() で判定する。

     *

     * 並列実行ロック:

     *   同時投稿が走った場合に「読み取り → +1 → 書き込み」が競合して

     *   カウンタが 1 増えない事故を防ぐため、ロックを取得してから更新する。

     *

     *   AIAP_Lock は wp_options への atomic INSERT (UNIQUE 制約) を使った

     *   真のロックなので、別プロセスが既に取得していれば必ず false を返す。

     *   - ロック取得成功時: try-finally で確実に release する

     *   - ロック取得失敗時 ($locked === false): それでもカウンタは更新する

     *     (理由: AIAP_Scheduler::run() の scheduler_run ロックにより cron 経路の

     *      並列実行は実質直列化されており、ここのロック失敗は極めて稀。

     *      取得失敗で更新を諦めると Free プランの投稿が過小カウントされ、

     *      実際の投稿数より少なく表示されるため、敢えて更新を続行する)

     */

    public static function increment_post_count() {

        // AIAP_Lock::with_lock がトークン管理 + 解放を内包。失敗時もカウンタは更新する

        // (CLAUDE.md「過小カウント回避」: 上位 scheduler_run ロックで並列化済のため衝突は稀)。

        $do_increment = function () {

            $counter           = self::get_counter();

            $counter['count']  = (int) $counter['count'] + 1;

            $counter['last']   = current_time( 'mysql' );

            update_option( self::OPTION_COUNTER, $counter, false );

        };



        if ( class_exists( 'AIAP_Lock' ) ) {

            AIAP_Lock::with_lock( AIAP_Lock_Keys::POST_COUNTER, 10, 10, 20000, function ( $acquired ) use ( $do_increment ) {

                $do_increment();

            } );

        } else {

            $do_increment();

        }

    }



    /**

     * 月跨ぎの自動リセット込みでカウンタを取得

     */

    private static function get_counter() {

        $current_month = wp_date( 'Y-m' );

        $counter       = get_option( self::OPTION_COUNTER, array() );



        if ( ! is_array( $counter ) || empty( $counter['month'] ) || $counter['month'] !== $current_month ) {

            // 月が変わった or 初回 → リセット

            $counter = array(

                'month' => $current_month,

                'count' => 0,

                'last'  => '',

            );

            update_option( self::OPTION_COUNTER, $counter, false );

        }



        return $counter;

    }



    /**

     * 制限到達時のログ用メッセージキー

     *

     * AIAP_Logger::add() に渡す英語ソース文字列。

     * 翻訳は po/mo 側で対応する。

     */

    public static function limit_reached_message() {

        return __(

            'Free plan monthly limit reached (10 posts). Upgrade to Pro for unlimited posting.',

            'ai-auto-poster'

        );

    }

}

