<?php
/**
 * VR票务插件 - 审计日志服务
 *
 * 记录所有敏感操作的防篡改审计日志
 *
 * @package vr_ticket\service
 */

namespace app\plugins\vr_ticket\service;

require_once __DIR__ . '/BaseService.php';

class AuditService
{
    // ========================
    // 操作类型常量（枚举）
    // ========================
    const ACTION_VERIFY          = 'verify';           // 核销票
    const ACTION_REFUND          = 'refund';           // 退款
    const ACTION_EXPORT          = 'export';           // 批量导出
    const ACTION_DISABLE_VERIFIER= 'disable_verifier'; // 禁用核销员
    const ACTION_ENABLE_VERIFIER = 'enable_verifier';  // 启用核销员
    const ACTION_DELETE_VERIFIER = 'delete_verifier';  // 删除核销员
    const ACTION_DELETE_TEMPLATE = 'delete_template';  // 删除座位模板
    const ACTION_DISABLE_TEMPLATE= 'disable_template'; // 禁用座位模板
    const ACTION_ENABLE_TEMPLATE = 'enable_template';  // 启用座位模板
    const ACTION_ISSUE_TICKET   = 'issue_ticket';     // 补发票（Phase 3）

    // ========================
    // 对象类型常量
    // ========================
    const TARGET_TICKET       = 'ticket';
    const TARGET_VERIFIER     = 'verifier';
    const TARGET_TEMPLATE     = 'seat_template';
    const TARGET_GOODS       = 'goods';

    // ========================
    // 日志记录入口（同步写入，异常不阻断主流程）
    // ========================

    /**
     * 记录审计日志
     *
     * @param string $action       操作类型（使用常量）
     * @param string $targetType   对象类型
     * @param int    $targetId     对象ID
     * @param array  $extra        附加数据（before/after 状态等）
     * @param string $targetDesc   对象描述（冗余字段，便于人工查询）
     * @return int|false           写入成功返回日志ID，失败返回 false
     */
    public static function log($action, $targetType, $targetId, $extra = [], $targetDesc = '')
    {
        try {
            $operatorId   = self::getOperatorId();
            $operatorName = self::getOperatorName();
            $clientIp     = self::getClientIp();
            $userAgent    = self::getUserAgent();
            $requestId    = self::getOrCreateRequestId();
            $createdAt    = BaseService::now();

            $id = \think\facade\Db::name(BaseService::table('audit_log'))->insertGetId([
                'action'        => $action,
                'operator_id'   => $operatorId,
                'operator_name' => $operatorName,
                'target_type'   => $targetType,
                'target_id'     => $targetId,
                'target_desc'   => $targetDesc ?: self::buildTargetDesc($targetType, $targetId),
                'client_ip'     => $clientIp,
                'user_agent'    => mb_substr($userAgent, 0, 512),
                'request_id'    => $requestId,
                'extra'         => empty($extra) ? null : json_encode($extra, JSON_UNESCAPED_UNICODE),
                'created_at'    => $createdAt,
            ]);

            return $id;
        } catch (\Throwable $e) {
            // 审计日志写入失败不阻断主业务流程，但记录警告
            BaseService::log('AuditService::log failed', [
                'action'     => $action,
                'targetType' => $targetType,
                'targetId'   => $targetId,
                'error'      => $e->getMessage(),
            ], 'warning');
            return false;
        }
    }

    // ========================
    // 便捷包装方法（核销操作）
    // ========================

    /**
     * 记录核销操作
     */
    public static function logVerify($ticketId, $ticketCode, $verifierId, $verifierName, $result, $oldStatus)
    {
        return self::log(
            self::ACTION_VERIFY,
            self::TARGET_TICKET,
            $ticketId,
            [
                'ticket_code'  => $ticketCode,
                'verifier_id' => $verifierId,
                'verifier'    => $verifierName,
                'old_status'  => $oldStatus,
                'result'      => $result,
            ],
            "票码: {$ticketCode}"
        );
    }

    /**
     * 记录导出操作
     */
    public static function logExport($goodsId, $filter, $count)
    {
        return self::log(
            self::ACTION_EXPORT,
            self::TARGET_GOODS,
            $goodsId,
            [
                'filter' => $filter,
                'count'  => $count,
            ],
            $goodsId > 0 ? "商品ID: {$goodsId}" : '全量导出'
        );
    }

    // ========================
    // 查询接口（供管理后台使用）
    // ========================

    /**
     * 查询审计日志（分页）
     *
     * @param array $params  查询参数：action, operator_id, target_type, target_id, date_from, date_to, page, limit
     * @return array
     */
    public static function search($params = [])
    {
        $where = [];

        if (!empty($params['action'])) {
            $where[] = ['action', '=', $params['action']];
        }
        if (!empty($params['operator_id'])) {
            $where[] = ['operator_id', '=', intval($params['operator_id'])];
        }
        if (!empty($params['target_type'])) {
            $where[] = ['target_type', '=', $params['target_type']];
        }
        if (!empty($params['target_id'])) {
            $where[] = ['target_id', '=', intval($params['target_id'])];
        }
        if (!empty($params['date_from'])) {
            $where[] = ['created_at', '>=', strtotime($params['date_from'])];
        }
        if (!empty($params['date_to'])) {
            $where[] = ['created_at', '<=', strtotime($params['date_to'] . ' 23:59:59')];
        }

        $page     = max(1, intval($params['page'] ?? 1));
        $pageSize = min(100, max(10, intval($params['limit'] ?? 20)));

        $result = \think\facade\Db::name(BaseService::table('audit_log'))
            ->where($where)
            ->order('id', 'desc')
            ->paginate($pageSize)
            ->toArray();

        // JSON 解析 extra 字段
        if (!empty($result['data'])) {
            foreach ($result['data'] as &$row) {
                if (!empty($row['extra'])) {
                    $row['extra'] = json_decode($row['extra'], true);
                }
            }
            unset($row);
        }

        return $result;
    }

    // ========================
    // 内部工具方法
    // ========================

    /**
     * 获取当前操作用户 ID
     */
    private static function getOperatorId()
    {
        // ShopXO admin session: $this->admin['id'] 在控制器中
        // 在服务层通过 session() 或 app() 获取
        $admin = session('admin');
        return isset($admin['id']) ? intval($admin['id']) : 0;
    }

    /**
     * 获取当前操作用户名称
     */
    private static function getOperatorName()
    {
        $admin = session('admin');
        return $admin['username'] ?? ($admin['name'] ?? '');
    }

    /**
     * 获取客户端真实 IP
     */
    private static function getClientIp()
    {
        $ip = request()->ip(0, true); // true = 穿透代理
        return $ip ?: '';
    }

    /**
     * 获取 User-Agent
     */
    private static function getUserAgent()
    {
        return request()->header('user-agent', '');
    }

    /**
     * 获取或创建请求追踪 ID（用于关联同一 HTTP 请求中的多个操作）
     */
    private static function getOrCreateRequestId()
    {
        static $requestId = null;
        if ($requestId === null) {
            $requestId = session('vr_ticket_request_id');
            if (empty($requestId)) {
                $requestId = self::generateRequestId();
                session('vr_ticket_request_id', $requestId);
            }
        }
        return $requestId;
    }

    /**
     * 生成唯一请求 ID
     */
    private static function generateRequestId()
    {
        return sprintf(
            '%s-%s-%04x-%04x-%04x',
            date('YmdHis'),
            substr(md5(uniqid((string) mt_rand(), true)), 0, 8),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
    }

    /**
     * 根据对象类型和 ID 构建描述文本
     */
    private static function buildTargetDesc($targetType, $targetId)
    {
        switch ($targetType) {
            case self::TARGET_TICKET:
                $ticket = \think\facade\Db::name(BaseService::table('tickets'))->where('id', $targetId)->find();
                return $ticket ? "票码: {$ticket['ticket_code']}" : "票ID: {$targetId}";
            case self::TARGET_VERIFIER:
                $verifier = \think\facade\Db::name(BaseService::table('verifiers'))->where('id', $targetId)->find();
                return $verifier ? "核销员: {$verifier['name']}" : "核销员ID: {$targetId}";
            case self::TARGET_TEMPLATE:
                $template = \think\facade\Db::name(BaseService::table('seat_templates'))->where('id', $targetId)->find();
                return $template ? "模板: {$template['name']}" : "模板ID: {$targetId}";
            default:
                return "{$targetType}:{$targetId}";
        }
    }
}
