<?php

namespace pictpostpersonal;

/**
 * TokenManager - ファイルベースのトークン管理クラス
 * 
 * セッショントークンの生成、検証、削除を行います。
 * トークンは30日間の有効期限を持ち、期限切れトークンは自動削除されます。
 */

class TokenManager
{
    /** @var string トークンファイルを保存するディレクトリ */
    private $token_dir;

    /** @var int トークンの有効期限（秒） - 30日 */
    private $token_lifetime = 2592000; // 30日 * 24時間 * 60分 * 60秒

    /** @var string トークンファイルの拡張子 */
    private $file_extension = '.php';

    /**
     * コンストラクタ
     * 
     * @param string $tokenDir トークンファイルを保存するディレクトリパス
     * @param int|null $tokenLifetime トークンの有効期限（秒）
     */
    public function __construct($token_dir, $token_lifetime = null)
    {
        $this->token_dir = rtrim($token_dir, '/');

        if ($token_lifetime !== null) {
            $this->token_lifetime = $token_lifetime;
        }

        // ディレクトリが存在しない場合は作成
        if (!is_dir($this->token_dir)) {
            if (!mkdir($this->token_dir, 0700, true)) {
                throw new \RuntimeException("トークンディレクトリの作成に失敗しました: {$this->token_dir}");
            }
        }

        // ディレクトリの書き込み権限をチェック
        if (!is_writable($this->token_dir)) {
            throw new \RuntimeException("トークンディレクトリに書き込み権限がありません: {$this->token_dir}");
        }
    }

    /**
     * 新しいトークンを生成
     * 
     * @param string $userId ユーザーID
     * @param array $metadata 追加のメタデータ（オプション）
     * @return string 生成されたトークン
     */
    public function generateToken($user_id, array $metadata = [])
    {
        // 期限切れトークンの削除
        $this->cleanupExpiredTokens();

        // セキュアなランダムトークンの生成
        $token = bin2hex(random_bytes(32));

        // トークンデータの構築
        $token_data = [
            'token' => $token,
            'user_id' => $user_id,
            'created_at' => time(),
            'expires_at' => time() + $this->token_lifetime,
            'last_accessed' => time(),
            'metadata' => $metadata
        ];

        // トークンファイルの保存
        $filename = $this->getTokenFilePath($token);
        if (!file_put_contents($filename, json_encode($token_data), LOCK_EX)) {
            throw new \RuntimeException('トークンファイルの保存に失敗しました');
        }

        // ファイル権限を設定（セキュリティ向上のため）
        chmod($filename, 0600);

        return $token;
    }

    /**
     * トークンを検証
     * 
     * @param string $token 検証するトークン
     * @param bool $updateLastAccessed 最終アクセス時刻を更新するか
     * @return array|false トークンデータまたはfalse（無効な場合）
     */
    public function validateToken($token, $update_last_accessed = true)
    {
        // トークン形式の基本検証
        if (!$this->isValidTokenFormat($token)) {
            return false;
        }

        $filepath = $this->getTokenFilePath($token);

        // ファイルが存在しない場合
        if (!file_exists($filepath)) {
            return false;
        }

        // トークンデータの読み込み
        $content = file_get_contents($filepath);
        $token_data = json_decode($content, true);

        if (!$token_data) {
            // 不正なJSONデータの場合はファイルを削除
            unlink($filepath);
            return false;
        }

        // 有効期限の確認
        if ($token_data['expires_at'] < time()) {
            // 期限切れトークンは削除
            unlink($filepath);
            return false;
        }

        // 最終アクセス時刻の更新
        if ($update_last_accessed) {
            $token_data['last_accessed'] = time();
            file_put_contents($filepath, json_encode($token_data), LOCK_EX);
        }

        return $token_data;
    }

    /**
     * 特定のトークンを削除
     * 
     * @param string $token 削除するトークン
     * @return bool 削除成功時true
     */
    public function revokeToken($token)
    {
        if (!$this->isValidTokenFormat($token)) {
            return false;
        }

        $filepath = $this->getTokenFilePath($token);

        if (file_exists($filepath)) {
            return unlink($filepath);
        }

        return false;
    }

    /**
     * 特定ユーザーの全トークンを削除
     * 
     * @param string $userId ユーザーID
     * @return int 削除したトークン数
     */
    public function revokeUserTokens($user_id)
    {
        $deleted_count = 0;

        // 全トークンファイルをスキャン
        $files = glob($this->token_dir . '/*' . $this->file_extension);

        foreach ($files as $file) {
            $content = file_get_contents($file);
            $token_data = json_decode($content, true);

            if ($token_data && isset($token_data['user_id']) && $token_data['user_id'] === $user_id) {
                if (unlink($file)) {
                    $deleted_count++;
                }
            }
        }

        return $deleted_count;
    }

    /**
     * 期限切れトークンを削除
     * 
     * @return int 削除したトークン数
     */
    public function cleanupExpiredTokens()
    {
        $deleted_count = 0;
        $current_time = time();

        $files = glob($this->token_dir . '/*' . $this->file_extension);

        foreach ($files as $file) {
            $content = file_get_contents($file);
            $token_data = json_decode($content, true);

            if (!$token_data) {
                // 不正なファイルは削除
                unlink($file);
                $deleted_count++;
                continue;
            }

            if (isset($token_data['expires_at']) && $token_data['expires_at'] < $current_time) {
                if (unlink($file)) {
                    $deleted_count++;
                }
            }
        }

        return $deleted_count;
    }

    /**
     * ユーザーのアクティブなトークンを取得
     * 
     * @param string $userId ユーザーID
     * @return array アクティブなトークンのリスト
     */
    public function getUserTokens($user_id)
    {
        $user_tokens = [];
        $current_time = time();

        $files = glob($this->token_dir . '/*' . $this->file_extension);

        foreach ($files as $file) {
            $content = file_get_contents($file);
            $token_data = json_decode($content, true);

            if (
                $token_data &&
                isset($token_data['user_id']) &&
                $token_data['user_id'] === $user_id &&
                $token_data['expires_at'] > $current_time
            ) {
                $user_tokens[] = $token_data;
            }
        }

        return $user_tokens;
    }

    /**
     * トークンの有効期限を延長
     * 
     * @param string $token トークン
     * @param int|null $extensionTime 延長時間（秒）。nullの場合はデフォルトの有効期限を使用
     * @return bool 成功時true
     */
    public function extendTokenExpiry($token, $extension_time = null)
    {
        $token_data = $this->validateToken($token, false);

        if (!$token_data) {
            return false;
        }

        $extension = $extension_time ?? $this->token_lifetime;
        $token_data['expires_at'] = time() + $extension;
        $token_data['last_accessed'] = time();

        $filepath = $this->getTokenFilePath($token);
        return file_put_contents($filepath, json_encode($token_data), LOCK_EX) !== false;
    }

    /**
     * トークン統計情報を取得
     * 
     * @return array 統計情報
     */
    public function getStatistics()
    {
        $stats = [
            'total_tokens' => 0,
            'expired_tokens' => 0,
            'active_tokens' => 0,
            'users_with_tokens' => 0,
            'oldest_token' => null,
            'newest_token' => null
        ];

        $current_time = time();
        $users = [];
        $oldest_time = PHP_INT_MAX;
        $newest_time = 0;

        $files = glob($this->token_dir . '/*' . $this->file_extension);

        foreach ($files as $file) {
            $content = file_get_contents($file);
            $token_data = json_decode($content, true);

            if (!$token_data) {
                continue;
            }

            $stats['total_tokens']++;

            if ($token_data['expires_at'] < $current_time) {
                $stats['expired_tokens']++;
            } else {
                $stats['active_tokens']++;

                if (isset($token_data['user_id'])) {
                    $users[$token_data['user_id']] = true;
                }

                if ($token_data['created_at'] < $oldest_time) {
                    $oldest_time = $token_data['created_at'];
                    $stats['oldest_token'] = $token_data['created_at'];
                }

                if ($token_data['created_at'] > $newest_time) {
                    $newest_time = $token_data['created_at'];
                    $stats['newest_token'] = $token_data['created_at'];
                }
            }
        }

        $stats['users_with_tokens'] = count($users);

        return $stats;
    }



    /**
     * トークン形式の検証
     * 
     * @param string $token トークン
     * @return bool 有効な形式の場合true
     */
    private function isValidTokenFormat($token)
    {
        // 16進数文字列で64文字（32バイト）であることを確認
        return preg_match('/^[a-f0-9]{64}$/i', $token) === 1;
    }

    /**
     * トークンファイルのパスを取得
     * 
     * @param string $token トークン
     * @return string ファイルパス
     */
    private function getTokenFilePath($token)
    {
        $safe_token = preg_replace('/[^a-f0-9]/i', '', $token);
        return $this->token_dir . '/' . $safe_token . $this->file_extension;
    }
}
