<?php

namespace App\Services\Settlement;

use App\Cache\FinanceCache;
use App\Consts\BaseConst;
use App\Consts\ErrorConst;
use App\Consts\FinanceConsts;
use App\Dao\Bank\BankDao;
use App\Dao\Bank\WithdrawDao;
use App\Dao\Settlement\BillDao;
use App\Dao\Settlement\FinancialDao;
use App\Facade\Site;
use App\Libs\Utils;
use App\Exceptions\ApiException;
use App\Models\Settlement\FinancialStats;
use Illuminate\Support\Facades\DB;

class SettlementService
{
    private $billDao;
    private $bankDao;
    private $financialDao;
    private $withdrawDao;

    public function __construct(
        BillDao      $billDao,
        BankDao      $bankDao,
        FinancialDao $financialDao,
        WithdrawDao  $withdrawDao
    )
    {
        $this->billDao      = $billDao;
        $this->bankDao      = $bankDao;
        $this->financialDao = $financialDao;
        $this->withdrawDao  = $withdrawDao;
    }

    /**
     * 结算列表
     *
     * @param $params
     * @return mixed
     */
    public function bills($params)
    {
        // 站点信息
        $channelId            = Site::getCurrentChannelId();
        $params['channel_id'] = $channelId;

        // 结算列表
        $bills = $this->billDao->getBills($params);

        // 导出逻辑
        $export = (int)getProp($params, 'export');
        if ($export) {
            $this->exportBills($bills);
        }

        return $bills;
    }

    /**
     * 结算单订单明细
     *
     * @param $date
     * @return mixed
     */
    public function billOrders($date)
    {
        // 站点信息
        $channelId = Site::getCurrentChannelId();

        // 查询订单
        return $this->billDao->billOrders($channelId, $date);
    }

    /**
     * 结算信息-汇总数据
     *
     * @return array
     */
    public function billsStat(): array
    {
        // 站点id
        $channelId = Site::getCurrentChannelId();

        // 站点提现信息
        $financialStat                = $this->financialDao->getChannelFinancialStat($channelId);
        $enableWithdrawalAmount       = getProp($financialStat, 'enable_withdrawal_amount_7day', 0.00); // t-7可提现余额
        $accumulativeWithdrawalAmount = getProp($financialStat, 'accumulative_withdrawal_amount', 0.00);
        $latestWithdrawalAmount       = getProp($financialStat, 'latest_withdrawal_amount', 0.00);
        $withdrawPendingAmount        = getProp($financialStat, 'withdraw_pending_amount', 0.00);
        $latestWithdrawalTime         = getProp($financialStat, 'latest_withdraw_time');

        // 充值总额
        // $totalRechargeAmount = $this->orderDao->getChannelRechargeAmount($channelId);

        // 充值总额（默认7日前数据）
        [$startDate, $endDate] = ['', date('Y-m-d', strtotime('-' . BaseConst::DATE_RANGE_DAYS . ' days'))];
        $totalRechargeAmountBefore7Days = $this->billDao->getBillsRechargeAmount($channelId, $startDate, $endDate);

        // 月充值总额（前7日数据不显示）
        $monthRechargeAmountWithout7Days = 0.00;
        $startDate                       = date('Y-m-01');
        if (date('j') > BaseConst::DATE_RANGE_DAYS) {
            $monthRechargeAmountWithout7Days = $this->billDao->getBillsRechargeAmount($channelId, $startDate, $endDate);
        }

        // 最近一笔提现中
        $latestWithdrawLog = $this->withdrawDao->getLatestWithdrawingCash($channelId);
        $withdrawingAmount = getProp($latestWithdrawLog, 'amount', 0.00);

        // 结算总额
        // $totalSettlementAmount = $accumulativeWithdrawalAmount + $enableWithdrawalAmount + $withdrawPendingAmount;

        // 站点统计
        return [
            'total_recharge_amount'               => 0.00,                                                // floor($totalRechargeAmount * 100) / 100, // 充值总额
            'total_recharge_amount_without_today' => 0.00,                                                // 充值总额（不含当日）
            'month_recharge_amount_without_today' => 0.00,                                                // 月充值总额（不含当日）
            'total_recharge_amount_without_7day'  => floor($totalRechargeAmountBefore7Days * 100) / 100,  // 充值总额（不含当日）
            'month_recharge_amount_without_7day'  => floor($monthRechargeAmountWithout7Days * 100) / 100, // 月充值总额（不含当日）
            'enable_withdrawal_amount'            => floor($enableWithdrawalAmount * 100) / 100,          // 可提现总余额
            'total_withdraw_amount'               => floor($accumulativeWithdrawalAmount * 100) / 100,    // 累计提现金额
            'total_settlement_amount'             => 0.00,                                                // floor($totalSettlementAmount * 100) / 100, // 结算总额
            'latest_withdrawal_amount'            => floor($latestWithdrawalAmount * 100) / 100,          // 最近一笔提现金额
            'latest_withdraw_time'                => $latestWithdrawalTime ?: '',                         // 最近一笔提现时间
            'withdrawing_amount'                  => floor($withdrawingAmount * 100) / 100,               // 提现中金额
        ];
    }

    /**
     * 提现列表
     *
     * @param $params
     * @return mixed
     */
    public function withdrawCashes($params)
    {
        // 站点id
        $channelId            = Site::getCurrentChannelId();
        $params['channel_id'] = $channelId;

        // 获取提现记录
        return $this->withdrawDao->getWithdrawCashes($params);
    }

    /**
     * 提现
     *
     * @param $params
     * @return array
     * @throws ApiException
     */
    public function applyWithDraw($params): array
    {
        dLog('withdraw')->info('applyWithDraw-1', $params);
        // 相关参数
        $channelId     = Site::getCurrentChannelId();
        $channelUserId = Site::getUid();
        $amount        = (float)getProp($params, 'amount');
        $cardId        = (int)getProp($params, 'card_id');
        $isCompany     = (int)getProp($params, 'is_company');

        // 金额不小于1
        if (!is_numeric($amount) || $amount < 1 || $cardId < 1) {
            Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
        }

        // 单笔提现下限
        if ($amount < FinanceConsts::WITH_DRAW_PRIVATE_MIN) {
            Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT);
        }

        // 单笔提现上限
        if ($amount >= FinanceConsts::WITH_DRAW_PRIVATE_MAX) {
            Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_MORE);
        }

        // 核对银行卡信息
        $bankAccount = $this->bankDao->getValidCashAccountById($cardId);
        if (empty($bankAccount)) {
            Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_ACCOUNT);
        }
        if ($bankAccount['is_company'] != $isCompany) {
            Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_NOT_MATCH);
        }
        if ((int)$bankAccount['channel_user_id'] != $channelUserId) {
            Utils::throwError(ErrorConst::PRIVATE_ACCOUNT_NO_ACCESS);
        }
        dLog('withdraw')->info('applyWithDraw-2', compact('bankAccount'));

        // 判断当前渠道今日是否已有提现
        $isWithDrawn = $this->checkChannelIsWithDrawByChannelId($channelId);
        if ($isWithDrawn) {
            Utils::throwError(ErrorConst::WITHDRAW_CASH_TODAY_USE);
        }
        dLog('withdraw')->info('applyWithDraw-3', compact('isWithDrawn'));

        // 判断账户可提现余额是否满足
        $financialStat        = $this->financialDao->getChannelFinancialStat($channelId);
        $enableWithDrawAmount = getProp($financialStat, 'enable_withdrawal_amount_7day'); // t-7可提现余额
        dLog('withdraw')->info('applyWithDraw-4', compact('financialStat', 'enableWithDrawAmount'));

        // 余额判断(-1 小于, 0 等于,  1 大于， 第3个参数必须带上)
        if (bccomp($amount, $enableWithDrawAmount, 2) >= 1) {
            Utils::throwError(ErrorConst::WITHDRAW_NOT_ENOUGH);
        }

        // 执行提现
        $result = $this->runWithDraw($channelId, $amount, $bankAccount, $financialStat);
        if (!$result) {
            Utils::throwError(ErrorConst::WITHDRAW_CASHES_FAILED);
        }

        return [
            'enable_amount'           => $enableWithDrawAmount - $amount,
            'withdraw_pending_amount' => $amount
        ];
    }

    /**
     * 执行提现
     *
     * @param                $channelId
     * @param                $amount
     * @param                $bankAccount
     * @param FinancialStats $financialStat
     * @return bool
     */
    private function runWithDraw($channelId, $amount, $bankAccount, FinancialStats $financialStat): bool
    {
        try {
            DB::beginTransaction();

            // 更新渠道可提现金额
            $financialStat->latest_withdrawal_amount      = $amount;               //最近提现金额
            $financialStat->enable_withdrawal_amount      -= $amount;              //修改可提现金额
            $financialStat->enable_withdrawal_amount_7day -= $amount;              //修改可提现金额
            $financialStat->withdraw_pending_amount       += $amount;              //修改提现中金额
            $financialStat->latest_withdraw_time          = date('Y-m-d H:i:s');   //修改提现时间
            $financialStat->save();

            // 创建提现单
            $this->withdrawDao->addWithDrawCashes([
                'distribution_channel_id'   => $channelId,
                'distribution_channel_name' => Site::getCurrentChannelName(),
                'tallage'                   => 0,
                'amount'                    => $amount,
                'status'                    => FinanceConsts::CHECK_PENDING,
                'is_company'                => (int)getProp($bankAccount, 'is_company'),
                'account_bank'              => getProp($bankAccount, 'account_bank'),
                'bank_account'              => getProp($bankAccount, 'card_number'),
                'account_name'              => getProp($bankAccount, 'account_name'),
                'pay_merchant_company_id'   => 1, // 目前只有一个公司主体
                'bank'                      => getProp($bankAccount, 'bank'),
            ]);

            DB::commit();

            // 添加渠道到今日提现队列中
            FinanceCache::sAddChannelIdToWithDraw($channelId, date('Ymd'));

            return true;
        } catch (\Exception $e) {
            dLog('withdraw')->info('runWithDraw-exception', [$e->getMessage()]);
            $msg = sprintf('异常-站点 %s 提现 %s 元失败', $channelId, $amount);
            sendNotice($msg);
            DB::rollBack();
            return false;
        }
    }

    /**
     * 校验渠道当日是否已有提现
     *
     * @param $channelId
     * @return bool
     */
    private function checkChannelIsWithDrawByChannelId($channelId): bool
    {
        // 缓存层判断
        $ymd = date('Ymd');
        if (FinanceCache::checkChannelIdIsInWithDraw($channelId, $ymd)) {
            return true;
        }

        // 数据库层判断
        [$startTime, $endTime] = [date('Y-m-d'), date('Y-m-d') . ' 23:59:59'];
        $checkRes = $this->withdrawDao->checkChannelIdIsInWithDraw($channelId, $startTime, $endTime);
        if (!$checkRes) {
            return false;
        }

        // 更新缓存
        FinanceCache::sAddChannelIdToWithDraw($channelId, $ymd);

        return true;
    }

    /**
     * 结算导出
     *
     * @param $bills
     * @return void
     */
    private function exportBills($bills)
    {
        // 设置超时时间
        set_time_limit(0);

        // 组装下载数据
        $data = [];
        if ($bills->count()) {
            foreach ($bills as $bill) {
                $data[] = [
                    getProp($bill, 'date'),
                    getProp($bill, 'recharge_amount'),
                    getProp($bill, 'rate') * 100 . '%',
                    getProp($bill, 'service_amount'),
                    getProp($bill, 'settlement_price'),
                ];
            }
        }

        $title = ['结算日期', '充值金额', '佣金比例', '支付通道费', '结算后金额'];
        exportExcel($data, $title, '结算信息-' . date("YmdHis"));
        exit();
    }
}