<?php
namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Db;

/**
 * 分销订单佣金结算定时任务
 *
 * 遍历已完成的分销订单，为每笔订单查找最近的两级代理商，
 * 并根据各代理商的 commission_rate 发放佣金奖励，写入 fa_shopro_agent_order_commission 表。
 *
 * 定时执行建议：每小时执行一次（默认查36小时内订单）
 * 命令：php think shopro:agent-order-commission
 *
 * 回溯补填（脚本异常停了N天后使用）：
 *   php think shopro:agent-order-commission --days=3       # 回溯3天
 *   php think shopro:agent-order-commission --start="2026-06-28 00:00:00" --end="2026-07-01 00:00:00"
 *
 * 注意：每笔订单写入前有防重复检查，回溯范围扩大不会导致重复发佣。
 */
class AgentOrderCommission extends Command
{
    /**
     * 控制台输出对象
     * @var Output
     */
    protected $output = null;

    /**
     * 本轮处理中受影响的代理商 ID 集合，用于结束时更新累计佣金
     * @var array [agent_id => true]
     */
    protected $affectedAgentIds = [];

    /**
     * 查询时间范围（小时）
     */
    const LOOKBACK_HOURS = 36;

    /**
     * 分页大小（每页订单数）
     */
    const PAGE_SIZE = 100;

    /**
     * 配置命令信息
     */
    protected function configure()
    {
        $this->setName('shopro:agent-order-commission')
             ->setDescription('分销订单佣金结算：遍历已完成订单，按代理商commission_rate发放佣金')
             ->addOption('days', 'd', Option::VALUE_OPTIONAL, '回溯天数(覆盖默认36h窗口)，例: --days=3', null)
             ->addOption('start', 's', Option::VALUE_OPTIONAL, '起始时间，格式: Y-m-d H:i:s 或时间戳，例: --start="2026-06-28 00:00:00"', null)
             ->addOption('end', 'e', Option::VALUE_OPTIONAL, '截止时间，格式: Y-m-d H:i:s 或时间戳，例: --end="2026-07-01 00:00:00"', null);
    }

    /**
     * 自定义日志记录方法
     *
     * @param string $level 日志级别: info/warning/error
     * @param string $message 日志内容
     */
    protected function customLog($level, $message)
    {
        $timestamp = date('Y-m-d H:i:s');
        $formattedMessage = "[{$timestamp}] [{$level}] {$message}";

        if ($this->output) {
            switch ($level) {
                case 'error':
                    $this->output->writeln("<error>{$formattedMessage}</error>");
                    break;
                case 'warning':
                    $this->output->writeln("<comment>{$formattedMessage}</comment>");
                    break;
                default:
                    $this->output->writeln("<info>{$formattedMessage}</info>");
                    break;
            }
        }

        custom_log($message, 0, 'agent_commission', $level);
    }

    /**
     * 命令执行入口方法
     *
     * @param Input $input
     * @param Output $output
     */
    protected function execute(Input $input, Output $output)
    {
        $this->output = $output;
        $this->customLog('info', '========== 开始执行分销订单佣金结算任务 ==========');

        // 建议：为 fa_shopro_agent_order_commission 添加唯一索引防止并发重复
        // ALTER TABLE fa_shopro_agent_order_commission ADD UNIQUE INDEX uk_order_agent (order_id, agent_id);

        try {
            // 结算时间范围（通过命令行参数或默认36h窗口）
            $endTime = time();
            $startTime = null;

            $startOption = $input->getOption('start');
            $endOption   = $input->getOption('end');
            $daysOption  = $input->getOption('days');

            if ($startOption) {
                // 精确指定起始时间
                $startTime = $this->parseTime($startOption);
            } elseif ($daysOption && intval($daysOption) > 0) {
                // 按天数回溯
                $startTime = $endTime - (intval($daysOption) * 86400);
            } else {
                // 默认：36小时窗口
                $startTime = $endTime - (self::LOOKBACK_HOURS * 3600);
            }

            if ($endOption) {
                $endTime = $this->parseTime($endOption);
            }

            // 时间范围校验
            if ($startTime >= $endTime) {
                $this->customLog('error', "时间范围无效: 起始时间 >= 截止时间");
                return;
            }

            $diffHours = round(($endTime - $startTime) / 3600, 1);
            $this->customLog('info', "查询时间范围: " . date('Y-m-d H:i:s', $startTime) . " ~ " . date('Y-m-d H:i:s', $endTime) . " (跨度: {$diffHours}小时)");

            // 1. 分页查询36小时内状态变更为 completed 的订单
            $page = 1;
            $totalProcessed = 0;
            $successCount = 0;
            $skipCount = 0;
            $errorCount = 0;

            while (true) {
                $orders = $this->getCompletedOrders($startTime, $endTime, $page, self::PAGE_SIZE);

                if (empty($orders)) {
                    $this->customLog('info', "第{$page}页无数据，分页遍历结束");
                    break;
                }

                $pageTotal = count($orders);
                $totalProcessed += $pageTotal;
                $this->customLog('info', "第{$page}页: 获取 {$pageTotal} 笔订单");

                if ($page === 1 && $pageTotal === 0) {
                    $this->customLog('info', '无待处理订单，任务结束');
                    return;
                }

                // 遍历订单，查找两级代理商并结算佣金
                foreach ($orders as $order) {
                    try {
                        $result = $this->processOrder($order);
                        if ($result === true) {
                            $successCount++;
                        } elseif ($result === 'skip') {
                            $skipCount++;
                        }
                    } catch (\Exception $e) {
                        $errorCount++;
                        $this->customLog('error', "订单[{$order['order_sn']}]处理异常: " . $e->getMessage());
                    }
                }

                // 如果本页数量少于分页大小，说明已是最后一页
                if ($pageTotal < self::PAGE_SIZE) {
                    $this->customLog('info', "第{$page}页数据不足分页大小，已是最后一页");
                    break;
                }

                $page++;
            }

            $this->customLog('info', "共处理 {$totalProcessed} 笔订单 - 成功: {$successCount}, 跳过: {$skipCount}, 异常: {$errorCount}");

            // 2. 所有订单佣金统计完毕后，更新代理商累计佣金总额
            $this->updateAgentsTotalCommission();

            $this->customLog('info', '========== 分销订单佣金结算任务执行完成 ==========');

        } catch (\Exception $e) {
            $this->customLog('error', '[分销订单佣金结算任务执行失败]');
            if (function_exists('format_log_error_custom_name')) {
                format_log_error_custom_name($e, '[分销订单佣金结算任务失败]', '', 'agent_commission');
            } else {
                $this->customLog('error', $e->getMessage() . "\n" . $e->getTraceAsString());
            }
        }
    }

    /**
     * 解析时间参数，支持时间戳数值和日期字符串两种格式
     *
     * @param string $value 时间值（时间戳或日期字符串）
     * @return int 时间戳
     * @throws \InvalidArgumentException
     */
    protected function parseTime($value)
    {
        // 纯数字 → 当作时间戳
        if (is_numeric($value) && strlen($value) >= 10) {
            return intval($value);
        }

        // 日期字符串 → strtotime 解析
        $timestamp = strtotime($value);
        if ($timestamp === false) {
            throw new \InvalidArgumentException("无法解析时间参数: {$value}，请使用 Y-m-d H:i:s 格式或时间戳");
        }

        return $timestamp;
    }

    /**
     * 分页获取36小时内状态变更为 completed 的订单列表
     *
     * 注意：使用 updatetime 而非 createtime 过滤。
     * 订单可能在创建很久之后才变为 completed，用 createtime 会导致遗漏。
     *
     * @param int $startTime 开始时间戳
     * @param int $endTime 结束时间戳
     * @param int $page 页码（从1开始）
     * @param int $pageSize 每页数量
     * @return array
     */
    protected function getCompletedOrders($startTime, $endTime, $page = 1, $pageSize = 100)
    {
        $orders = Db::name('shopro_order')
            ->alias('o')
            ->field('o.id, o.order_sn, o.user_id, o.pay_fee, o.order_amount, o.goods_amount, o.status, o.createtime, o.updatetime, o.ext')
            ->where('o.status', 'completed')
            ->where('o.updatetime', '>=', $startTime)
            ->where('o.updatetime', '<=', $endTime)
            ->where('o.pay_fee', '>', 0)
            // 排除已删除的订单
            ->whereNull('o.deletetime')
            ->order('o.id', 'asc')
            ->page($page, $pageSize)
            ->select();

        return $orders ?: [];
    }

    /**
     * 处理单笔订单的佣金结算
     *
     * @param array $order 订单数据
     * @return string|bool 'skip'=跳过, true=成功
     */
    protected function processOrder($order)
    {
        $orderId = $order['id'];
        $orderSn = $order['order_sn'];
        $buyerUserId = $order['user_id'];
        $payFee = floatval($order['pay_fee']);

        // 获取订单商品信息
        $orderItems = $this->getOrderItems($orderId);

        // 查找两级代理商
        $agents = $this->findAgentChain($buyerUserId, 2);

        if (empty($agents)) {
            $this->customLog('info', "订单[{$orderSn}]跳过: 未找到有效的上级代理商, 买家[{$buyerUserId}]");
            return 'skip';
        }

        $processedCount = 0;

        foreach ($agents as $agentInfo) {
            $agentUserId = $agentInfo['user_id'];
            $agentLevel = $agentInfo['level']; // 1=一级, 2=二级

            // 从 fa_shopro_agent 表获取该代理商的佣金比例及记录ID
            $agentRecord = Db::name('shopro_agent')
                ->where('user_id', $agentUserId)
                ->field('id, user_id, commission_rate')
                ->find();

            if (!$agentRecord) {
                $this->customLog('warning', "订单[{$orderSn}]代理商user_id[{$agentUserId}]在shopro_agent表中不存在, 跳过");
                continue;
            }

            $recordAgentId = $agentRecord['id'];
            $commissionRate = floatval($agentRecord['commission_rate']);

            if ($commissionRate <= 0) {
                $this->customLog('warning', "订单[{$orderSn}]代理商[{$recordAgentId}]commission_rate为0或未设置, 跳过");
                continue;
            }

            // 计算佣金：订单支付金额 × commission_rate%
            $commissionAmount = round($payFee * ($commissionRate / 100), 2);

            if ($commissionAmount <= 0) {
                $this->customLog('warning', "订单[{$orderSn}]代理商[{$recordAgentId}]计算佣金为0, 跳过");
                continue;
            }

            // 获取订单关联的商品信息（取第一个商品作为代表）
            $goodsInfo = !empty($orderItems) ? $orderItems[0] : [];
            $goodsPayPrice = !empty($orderItems) ? array_sum(array_column($orderItems, 'pay_fee')) : $payFee;

            // 获取收货地址中的省份信息
            $provinceInfo = $this->getOrderProvince($order['ext'] ?? '');

            // 构建佣金记录数据
            $data = [
                'agent_id'                => $recordAgentId,
                'agent_user_id'          => $agentRecord['user_id'] ?? $agentUserId,
                'order_id'               => $orderId,
                'order_sn'               => $orderSn,
                'buyer_user_id'           => $buyerUserId,
                'receive_province_id'     => $provinceInfo['province_id'] ?? 0,
                'receive_province_name'   => $provinceInfo['province_name'] ?? '',
                'goods_id'               => $goodsInfo['goods_id'] ?? 0,
                'goods_title'            => $goodsInfo['goods_title'] ?? '',
                'goods_pay_price'        => $goodsPayPrice,
                'commission_rate_snapshot' => $commissionRate,
                'commission_amount'       => $commissionAmount,
                'order_status'           => 'completed',
                'aftersale_order_sn'      => '',
                'order_confirm_time'      => $order['updatetime'],
                'order_createtime'        => $order['createtime'],
                'status'                  => 0,  // 0=待结算
                'createtime'              => time(),
                'updatetime'              => time(),
            ];

            // 事务包裹：检查 + 写入，防止并发时重复插入
            $inserted = false;
            Db::startTrans();
            try {
                // 在事务内再次检查（利用事务的隔离性增强并发安全）
                $exists = Db::name('shopro_agent_order_commission')
                    ->where('order_id', $orderId)
                    ->where('agent_id', $recordAgentId)
                    ->where('status', 'in', [0, 1])
                    ->lock(true)  // 行级锁，进一步阻止并发
                    ->find();

                if ($exists) {
                    Db::rollback();
                    $this->customLog('info', "订单[{$orderSn}]代理商[{$recordAgentId}]第{$agentLevel}级已存在佣金记录(事务内), 跳过");
                    continue;
                }

                // 写入佣金表
                Db::name('shopro_agent_order_commission')->insert($data);
                Db::commit();
                $inserted = true;
            } catch (\PDOException $e) {
                Db::rollback();
                // 捕获唯一索引冲突（如有索引），说明真的重复了
                if (stripos($e->getMessage(), 'Duplicate entry') !== false) {
                    $this->customLog('info', "订单[{$orderSn}]代理商[{$recordAgentId}]唯一索引冲突，已存在记录, 跳过");
                } else {
                    $this->customLog('error', "订单[{$orderSn}]写入异常: " . $e->getMessage());
                }
                continue;
            } catch (\Exception $e) {
                Db::rollback();
                $this->customLog('error', "订单[{$orderSn}]事务异常: " . $e->getMessage());
                continue;
            }

            if ($inserted) {
                $this->customLog('info', "订单[{$orderSn}]第{$agentLevel}级代理商[{$recordAgentId}]佣金: ¥{$commissionAmount} (订单金额: ¥{$payFee})");
                $this->affectedAgentIds[$recordAgentId] = true;
                $processedCount++;
            }
        }

        return $processedCount > 0 ? true : 'skip';
    }

    /**
     * 获取订单商品列表
     *
     * @param int $orderId 订单ID
     * @return array
     */
    protected function getOrderItems($orderId)
    {
        return Db::name('shopro_order_item')
            ->where('order_id', $orderId)
            ->field('id, goods_id, goods_title, goods_price, goods_num, pay_fee')
            ->select() ?: [];
    }

    /**
     * 递归查找用户的最近两级代理商
     *
     * 从买家开始向上追溯 parent_user_id，检查每一级是否为有效代理商，
     * 最多向上追溯两级。仅收集状态为 normal 或 freeze 的代理商。
     *
     * @param int $buyerUserId 买家用户ID
     * @param int $maxLevel 最大追溯层级（默认2级）
     * @return array [['user_id' => xxx, 'level' => 1], ...]
     */
    protected function findAgentChain($buyerUserId, $maxLevel = 2)
    {
        $agents = [];
        $currentUserId = $buyerUserId;
        $currentLevel = 1;

        while ($currentLevel <= $maxLevel) {
            // 查找当前用户的上级
            $user = Db::name('user')
                ->where('id', $currentUserId)
                ->field('id, parent_user_id')
                ->find();

            if (!$user || empty($user['parent_user_id']) || $user['parent_user_id'] <= 0) {
                break;
            }

            $parentUserId = $user['parent_user_id'];

            // 检查上级是否为有效代理商
            $agent = Db::name('shopro_commission_agent')
                ->where('user_id', $parentUserId)
                ->where('status', 'in', ['normal', 'freeze'])
                ->field('user_id, level')
                ->find();

            if ($agent) {
                $agents[] = [
                    'user_id'       => $agent['user_id'],
                    'agent_user_id' => $agent['user_id'],
                    'level'         => $currentLevel,
                ];
            }

            // 继续向上追溯（即使上级不是代理商，也继续向上找）
            $currentUserId = $parentUserId;
            $currentLevel++;
        }

        return $agents;
    }

    /**
     * 获取订单的收货省份信息
     *
     * @param string $ext 订单 ext JSON 字段值
     * @return array|null
     */
    protected function getOrderProvince($ext)
    {
        if (empty($ext)) {
            return null;
        }

        $ext = json_decode($ext, true);
        if (is_array($ext) && isset($ext['address'])) {
            $address = $ext['address'];
            if (isset($address['province_id'])) {
                return [
                    'province_id'   => $address['province_id'],
                    'province_name' => $address['province_name'] ?? '',
                ];
            }
        }

        return null;
    }

    /**
     * 更新本轮受影响代理商的累计佣金总额
     *
     * 从 fa_shopro_agent_order_commission 表汇总每个代理商的 commission_amount，
     * 回写到 fa_shopro_agent.total_commission 字段。
     */
    protected function updateAgentsTotalCommission()
    {
        if (empty($this->affectedAgentIds)) {
            $this->customLog('info', '本轮无新增佣金记录，跳过更新代理商累计佣金');
            return;
        }

        $agentIds = array_keys($this->affectedAgentIds);
        $agentCount = count($agentIds);
        $this->customLog('info', "开始更新 {$agentCount} 个代理商的累计佣金总额...");

        $updatedCount = 0;
        foreach ($agentIds as $agentId) {
            try {
                // 汇总该代理商所有佣金记录
                $total = Db::name('shopro_agent_order_commission')
                    ->where('agent_id', $agentId)
                    ->sum('commission_amount');

                $total = round(floatval($total), 2);

                // 更新 fa_shopro_agent 累计佣金
                Db::name('shopro_agent')
                    ->where('id', $agentId)
                    ->update([
                        'total_commission' => $total,
                        'updatetime'       => time(),
                    ]);

                $updatedCount++;
                $this->customLog('info', "代理商[{$agentId}]累计佣金更新为: ¥{$total}");
            } catch (\Exception $e) {
                $this->customLog('error', "代理商[{$agentId}]累计佣金更新失败: " . $e->getMessage());
            }
        }

        $this->customLog('info', "累计佣金更新完成: {$updatedCount}/{$agentCount}");
    }
}
