protected function configure()
{
    // 指令配置
    $this->setName('snap')
        ->setDescription('构建统计快照')
        ->addArgument('start', Argument::OPTIONAL, '开始日期')
        ->addArgument('end', Argument::OPTIONAL, '结束日期');
}

protected function execute(Input $input, Output $output)
{
    $startArg = $input->getArgument('start');
    $endArg   = $input->getArgument('end');

    if ($startArg && $endArg) {
        # 补历史数据
        $this->runHistory($startArg, $endArg, $output);
    } else {
        # 正常跑(昨天)
        $baseTime = strtotime('-1 day');
        $this->runOnce($baseTime);
    }

    $output->writeln('<info>snap 执行完成</info>');
}
protected function runHistory(string $startDate, string $endDate, Output $output)
{
    $start = strtotime($startDate);
    $end   = strtotime($endDate);

    if ($start > $end) {
        $output->writeln('<error>开始日期不能大于结束日期</error>');
        return;
    }

    $shopIds = Shop::where('status', 1)->column('id');

    for ($t = $start; $t <= $end; $t = strtotime('+1 day', $t)) {

        $baseDate = date('Y-m-d', $t);

        foreach ($shopIds as $shopId) {
            $baseTimeForSnap = strtotime('+1 day', $t);
            $this->updatePeriodSnapshots($shopId, $baseTimeForSnap);
        }

        $output->writeln("已处理 {$baseDate}");
    }
}
protected function runOnce(int $baseTime)
{

    $shopIds = Shop::where('status', 1)->column('id');

    foreach ($shopIds as $shopId) {
        $this->updatePeriodSnapshots($shopId, $baseTime);
    }
}

protected function updatePeriodSnapshots($shopId, int $baseTime)
{

    $periods = CheckEmployee::getPeriodsArray($baseTime);

    foreach ($periods as $period) {
        $this->updatePeriodSnapshot(
            $period['type'],
            $period['start'],
            $period['name'],
            $shopId
        );
    }
}

public static function getPeriodsArray($baseTime)
{
    $periods = [];
    $baseDate = date('Y-m-d', $baseTime);
    # ---------------- 日 ----------------
    $periods[] = [
        'type' => 1,
        'start' => date('Y-m-d', strtotime('-1 day', $baseTime)),
        'name' => '日'
    ];

    # ---------------- 周(基准时间是周一) ----------------
    if (date('N', $baseTime) == 1) {
        $lastMonday = date('Y-m-d', strtotime('last monday', $baseTime));
        $periods[] = ['type' => 2, 'start' => $lastMonday, 'name' => '周'];
    }

    # ---------------- 月(基准时间是 1 号) ----------------
    if (date('d', $baseTime) == '01') {
        $lastMonth = date('Y-m-01', strtotime('last month', $baseTime));
        $periods[] = ['type' => 3, 'start' => $lastMonth, 'name' => '月'];
    }

    # ---------------- 季度 ----------------
    if (self::isQuarterStart($baseDate)) {
        $lastQuarter = self::getLastQuarterStart($baseDate);
        $periods[] = ['type' => 4, 'start' => $lastQuarter, 'name' => '季'];
    }

    # ---------------- 年 ----------------
    if (date('m-d', $baseTime) === '01-01') {
        $lastYear = (date('Y', $baseTime) - 1) . '-01-01';
        $periods[] = ['type' => 5, 'start' => $lastYear, 'name' => '年'];
    }

    return $periods;
}

protected function updatePeriodSnapshot($type, $startDate, $periodName, $shopId)
{

    $periodEnd = $this->getPeriodEndDate($startDate, $type);

    $startTime = strtotime($startDate);
    $endTime   = $periodEnd == $startDate
        ? strtotime($periodEnd . ' 23:59:59')
        : strtotime($periodEnd);

    $uniqueSign = CheckEmployee::generateUniqueSign($startTime, $type)[2];
    $exists = CheckEmployee::where([
        'shop_id'     => $shopId,
        'type'        => $type,
        'unique_sign' => $uniqueSign,
    ])->find();
    if ($exists) {
        Log::info("{$periodName} 快照已存在,跳过:{$startDate}");
        return;
    }


    Log::info("开始更新 {$periodName} 快照: {$startDate} 至 {$periodEnd}");

    # 公共 where
    $baseWhere = [
        ['status', '=', 2],
        ['shop_id', '=', $shopId],
        ['create_time', 'between', [$startTime, $endTime]],
    ];

    # 一次查所有订单数据
    $orders = Order::where($baseWhere)->select()->toArray();

    if (!$orders) {
        Log::info("无订单数据,无需生成快照");
        return;
    }

    # 按员工 + 设备号 分组
    $grouped = [];
    foreach ($orders as $o) {
        $key = $o['op_employee_id'] . '_' . $o['device_num'];
        $grouped[$key][] = $o;
    }

    # 统计新会员数
    $newMemberNum = Member::where(['shop_id' => $shopId])
        ->whereBetweenTime('create_time', $startTime, $endTime)
        ->count();

    $addData = [];

    foreach ($grouped as $key => $list) {

        [$employeeId, $deviceNum] = explode('_', $key);
        $orderIds = array_column($list, 'id');

        $stat = CheckEmployee::getCheckEmployeeData(
            $list,
            $orderIds,
            $shopId,
            $startTime,
            $endTime
        );

        $addData[] = [
            'shop_id' => $shopId,
            'device_num' => $deviceNum,
            'employee_id' => $employeeId,
            'start_time' => $startTime,
            'end_time' => $endTime,
            'type' => $type,
            'unique_sign' => $uniqueSign,

            'cash_money' => $stat['cashMoney'],
            'remain_money' => $stat['remainMoney'],
            'saving_money' => $stat['savingMoney'],
            'credit_money' => $stat['creditMoney'],
            'scan_money' => $stat['scanMoney'],

            'cash_order_num' => $stat['cashOrderNum'],
            'scan_order_num' => $stat['scanOrderNum'],
            'remain_order_num' => $stat['remainOrderNum'],
            'saving_order_num' => $stat['savingOrderNum'],
            'credit_order_num' => $stat['creditOrderNum'],

            'total_order_num' => $stat['totalOrderNum'],
            'new_member_num' => $newMemberNum,

            'quick_money' => $stat['quickMoney'],
            'sale_income' => $stat['saleIncome'],
            'com_income' => $stat['comIncome'],
            'service_money' => $stat['serviceMoney'],
            'normal_money' => $stat['normalMoney'],

            'total_charge_money' => $stat['totalChargeMoney'],
            'total_charge_times_money' => $stat['totalChargeTimesMoney'],
            'total_continue_money' => $stat['totalContinueMoney'],
            'total_combo_money' => $stat['totalComboMoney'],
            'total_refund_money' => $stat['totalRefundMoney'],
            'vip_money' => $stat['vipMoney'],
        ];
    }

    $this->saveEmployeeSnapshots($addData, $shopId, $type, $startTime,$endTime);

    Log::info("{$periodName} 快照更新完成");
}

protected function getPeriodEndDate($startDate, $periodType)
{
    $start = strtotime($startDate);

    switch ($periodType) {
        case 2: # 周
            return date('Y-m-d', $start + 6 * 86400);
        case 3: # 月
            return date('Y-m-t', $start);
        case 4: # 季
            $month = date('m', $start);
            $year = date('Y', $start);
            $endMonth = $month + 2;
            if ($endMonth > 12) {
                $endMonth -= 12;
                $year++;
            }
            $endMonth = str_pad((string)$endMonth, 2, '0', STR_PAD_LEFT);
            return date('Y-m-t', strtotime("{$year}-{$endMonth}-01"));
        case 5: # 年
            return date('Y-12-31', $start);
        default:
            return $startDate;
    }
}

public static function generateUniqueSign($timestamp, $type)
{
    $startTime = 0;
    $endTime = 0;
    $uniqueSign = '';
    $year = date('Y', $timestamp);
    if ($type == 1) {
        $uniqueSign = date('Y-m-d', $timestamp);
        $startTime = strtotime($uniqueSign . ' 00:00:00');
        $endTime = strtotime($uniqueSign . ' 23:59:59');
    }
    if ($type == 2) {
        $week = date('W', $timestamp);
        $uniqueSign = "{$year}-W{$week}";
        $startTime = self::getStartTimeByWeekYear($week, $year);
        $endTime = self::getEndTimeByWeekYear($week, $year);
    }
    if ($type == 3) {
        $uniqueSign = date('Y-m', $timestamp);
        $startTime = self::getStartTimeByYearMonth($year, date('n', $timestamp));
        $endTime = self::getEndTimeByYearMonth($year, date('n', $timestamp));
    }
    if ($type == 4) {
        $quarter = self::getQuarterNum($timestamp);
        $uniqueSign = "{$year}-Q{$quarter}";
        $startTime = self::getStartTimeByQuarter($year, $quarter);
        $endTime = self::getEndTimeByQuarter($year, $quarter);
    }
    if ($type == 5) {
        $year = date('Y', $timestamp);
        $uniqueSign = $year;
        $startTime = self::getStartTimeByYear($year);
        $endTime = self::getEndTimeByYear($year);
    }
    return [$startTime, $endTime, $uniqueSign];
}

public static function getCheckEmployeeData(array $orders, array $orderIds, int $shopId, int $startTime, int $endTime)
{
    # -------- 金额累加 --------
    $cashMoney = array_sum(array_column($orders, 'cash'));
    $remainMoney = array_sum(array_column($orders, 'remain_money'));
    $scanMoney = array_sum(array_column($orders, 'scan'));
    $savingMoney = array_sum(array_column($orders, 'saving'));
    $creditMoney = array_sum(array_column($orders, 'credit'));

    # 找零金额
    $roundMoney = array_sum(array_column($orders, 'round_down_money'));
    $cashMoney -= $roundMoney;

    # -------- 订单数量 --------
    $totalOrderNum = count($orders);

    # -------- 支付方式订单数 --------
    $cashOrderNum = $scanOrderNum = $remainOrderNum = $savingOrderNum = $creditOrderNum = 0;

    # -------- 按订单类型金额 --------
    $totalChargeMoney = 0;
    $totalChargeTimesMoney = 0;
    $totalContinueMoney = 0;
    $totalComboMoney = 0;
    $vipMoney = 0;
    $totalRefundMoney = 0;

    # -------- 总金额 --------
    $totalMoney = 0;

    foreach ($orders as $o) {

        $totalMoney += $o['real_money'];

        # 支付方式
        if ($o['pay_type'] & 1) $cashOrderNum++;
        if ($o['pay_type'] & 2) $scanOrderNum++;
        if ($o['pay_type'] & 4) $remainOrderNum++;
        if ($o['pay_type'] & 8) $savingOrderNum++;
        if ($o['pay_type'] & 16) $creditOrderNum++;

        # 订单类型
        switch ($o['order_type']) {
            case 3:
                $totalChargeMoney += $o['real_money'];
                break;
            case 4:
                $totalChargeTimesMoney += $o['real_money'];
                break;
            case 6:
                $totalRefundMoney += $o['real_money'];
                break;
            case 7:
            case 8:
                $totalContinueMoney += $o['real_money'];
                break;
            case 9:
                $totalComboMoney += $o['real_money'];
                break;
            case 10:
                $vipMoney += $o['real_money'];
                break;
        }
    }

    # 综合 / 销售收入
    $comIncome = $totalMoney + $totalRefundMoney - $remainMoney;
    $saleIncome = $comIncome - $totalChargeMoney;

    # -------- 商品类金额 --------
    $serviceMoney = OrderDetail::alias('od')
        ->join('goods g', 'g.id = od.goods_id')
        ->whereBetween('od.create_time', [$startTime, $endTime])
        ->whereIn('od.order_id', $orderIds)
        ->whereIn('g.goods_type', [2, 3, 4, 6])
        ->sum(Db::raw('goods_num * goods_sale_price'));

    $normalMoney = OrderDetail::alias('od')
        ->join('goods g', 'g.id = od.goods_id')
        ->whereBetween('od.create_time', [$startTime, $endTime])
        ->whereIn('od.order_id', $orderIds)
        ->whereIn('g.goods_type', [1, 5])
        ->sum(Db::raw('goods_num * goods_sale_price'));

    $quickMoney = OrderDetail::where([
        ['order_id', 'in', $orderIds],
        ['goods_id', '=', -2],
        ['order_band_id', '=', 0],
    ])->sum(Db::raw('goods_num * goods_sale_price'));

    return compact(
        'serviceMoney', 'normalMoney', 'quickMoney',
        'saleIncome', 'comIncome',
        'totalChargeMoney', 'totalChargeTimesMoney', 'totalRefundMoney',
        'totalContinueMoney', 'totalComboMoney', 'vipMoney',
        'totalOrderNum',
        'cashMoney', 'remainMoney', 'savingMoney', 'creditMoney', 'scanMoney',
        'cashOrderNum', 'scanOrderNum', 'remainOrderNum', 'savingOrderNum', 'creditOrderNum'
    );
}

protected function saveEmployeeSnapshots(array $employeeRows, int $shopId, int $type, int $startTime, int $endTime)
{
    # 保存员工 + 设备 -- 层级3
    CheckEmployee::saveAll($employeeRows);

    # 单店汇总 -- 层级2
    $shopRow = $this->buildShopSummary($employeeRows, $shopId, $type, $startTime,$endTime);
    if ($shopRow) {
        CheckEmployee::create($shopRow);
    }

    # 全店汇总 -- 层级1
    $totalRow = $this->buildTotalSummary($employeeRows, $type, $startTime,$endTime);
    if ($totalRow) {
        CheckEmployee::create($totalRow);
    }
}
protected function buildShopSummary(array $rows, int $shopId, int $type, int $startTime, int $endTime)
{
    if (empty($rows)) return null;

    $sum = $this->sumSnapshotRows($rows);

    return array_merge($sum, [
        'shop_id'     => $shopId,
        'device_num' => -1,
        'employee_id'=> -1,
        'type'       => $type,
        'unique_sign'=> CheckEmployee::generateUniqueSign($startTime, $type)[2],
        'start_time'   => $startTime,
        'end_time'     => $endTime,
    ]);
}
protected function buildTotalSummary(array $rows, int $type, int $startTime, int $endTime)
{
    if (empty($rows)) {
        return null;
    }

    $sum = $this->sumSnapshotRows($rows);

    return array_merge($sum, [
        'shop_id'      => 0,
        'device_num'  => 0,
        'employee_id' => 0,
        'type'        => $type,
        'unique_sign' => CheckEmployee::generateUniqueSign($startTime, $type)[2],
        'start_time'   => $startTime,
        'end_time'     => $endTime,
    ]);
}


protected function sumSnapshotRows(array $rows)
{
    $fields = [
        'cash_money','remain_money','saving_money','credit_money','scan_money',
        'cash_order_num','scan_order_num','remain_order_num','saving_order_num','credit_order_num',
        'total_order_num','new_member_num','quick_money','sale_income','com_income',
        'service_money','normal_money','total_charge_money','total_charge_times_money',
        'total_continue_money','total_combo_money','total_refund_money','vip_money'
    ];

    $sum = array_fill_keys($fields, 0);

    foreach ($rows as $row) {
        foreach ($fields as $f) {
            $sum[$f] += $row[$f] ?? 0;
        }
    }

    return $sum;
}