<?php

namespace Modules\Payment\Models;

use App\Models\Model;
use App\Traits\OrderByIdDesc;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Modules\Payment\Events\Paid;
use Modules\Payment\Facades\Pay;
use Modules\User\Models\User;
use Modules\User\Traits\BelongsToUser;

class Payment extends Model
{

    use BelongsToUser,
        OrderByIdDesc;

    protected $table = 'payments';

    protected $dates = [
        'paid_at',
    ];

    const STATUS_NOPAY    = 'nopay';
    const STATUS_SUCCESS  = 'success';
    const STATUS_FAIL     = 'fail';
    const STATUS_REFUND   = 'refund';
    const STATUS_CLOSED   = 'closed';
    const STATUS_REVOKED  = 'revoked';
    const STATUS_ERROR    = 'error';
    const STATUS_FINISHED = 'finished';

    const STATUS_MAP = [
        self::STATUS_NOPAY    => '未支付',
        self::STATUS_SUCCESS  => '支付成功',
        self::STATUS_FAIL     => '支付失败',
        self::STATUS_REFUND   => '转入退款',
        self::STATUS_CLOSED   => '支付关闭',
        self::STATUS_REVOKED  => '支付撤销',
        self::STATUS_ERROR    => '支付错误',
        self::STATUS_FINISHED => '支付完结',
    ];

    const STATUS_LABEL_MAP = [
        self::STATUS_NOPAY    => 'default',
        self::STATUS_SUCCESS  => 'success',
        self::STATUS_FAIL     => 'warning',
        self::STATUS_REFUND   => 'info',
        self::STATUS_CLOSED   => 'primary',
        self::STATUS_REVOKED  => 'primary',
        self::STATUS_ERROR    => 'danger',
        self::STATUS_FINISHED => 'success',
    ];

    /**
     * 支付方式
     */
    const DRIVER_ALIPAY = 'alipay';
    const DRIVER_WECHAT = 'wechat';
    const DRIVER_SCORE  = 'score';

    const DRIVER_MAP = [
        self::DRIVER_ALIPAY => '支付宝',
        self::DRIVER_WECHAT => '微信支付',
        self::DRIVER_SCORE  => '水滴支付',
    ];

    const DRIVER_LABEL_MAP = [
        self::DRIVER_ALIPAY => 'info',
        self::DRIVER_WECHAT => 'success',
    ];

    protected static function boot()
    {
        parent::boot();

        self::creating(function ($model) {
            $time = explode(' ', microtime());

            $counter = $model->whereDate('created_at', Carbon::today())->count() + 1;

            $len = Setting::orderByDesc('in_use')->value('trade_id_counter_length');

            $len = $len < 6 ? 6 : $len;
            $len = $len > 16 ? 16 : $len;

            $model->trade_id = date('YmdHis').
                sprintf('%06d', $time[0] * 1e6).
                sprintf('%0'.$len.'d', $counter);

            $model->state = self::STATUS_NOPAY;
        });
    }

    /**
     * Notes: 支付渠道
     *
     * @Author: 玄尘
     * @Date  : 2021/5/18 11:14
     * @return string
     */
    public function getDriverTextAttribute(): string
    {
        return self::DRIVER_MAP[$this->driver];
    }

    /**
     * Notes: 支付状态
     *
     * @Author: 玄尘
     * @Date  : 2021/5/18 11:17
     * @return string
     */
    public function getStateTextAttribute(): string
    {
        return self::STATUS_MAP[$this->state];
    }

    /**
     * Notes   : 要支付的模型
     *
     * @Date   : 2021/4/21 1:59 下午
     * @Author : < Jason.C >
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function order(): MorphTo
    {
        return $this->morphTo();
    }

    /**
     * Notes   : 结果通知
     *
     * @Date   : 2021/4/23 11:49 上午
     * @Author : < Jason.C >
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function notifies(): HasMany
    {
        return $this->hasMany(PaymentNotify::class);
    }

    /**
     * Notes   : 退款单
     *
     * @Date   : 2021/6/1 11:13 上午
     * @Author : < Jason.C >
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function refunds(): HasMany
    {
        return $this->hasMany(Refund::class);
    }

    /**
     * Notes   : 退款状态
     *
     * @Date   : 2021/6/1 11:40 上午
     * @Author : < Jason.C >
     * @return string
     */
    public function getRefundStatusTextAttribute(): string
    {
        if ($this->refunds->count()) {
            return ($this->refunds->sum('total') == $this->total) ? '全额退款' : '部分退款';
        } else {
            return '';
        }
    }

    /**
     * Notes   : 直接通过 order 来设置关联的支付订单
     *
     * @Date   : 2021/4/21 12:43 下午
     * @Author : < Jason.C >
     * @param  \App\Models\Model  $order
     */
    public function setOrderAttribute(Model $order)
    {
        $this->attributes['order_type'] = get_class($order);
        $this->attributes['order_id']   = $order->getKey();
    }

    /**
     * Notes   : 通过模型来设置所属用户
     *
     * @Date   : 2021/4/21 1:48 下午
     * @Author : < Jason.C >
     * @param  \Modules\User\Models\User  $user
     */
    public function setUserAttribute(User $user)
    {
        $this->attributes['user_id'] = $user->getKey();
    }

    /**
     * Notes   : 支付成功，调用的方法
     *
     * @Date   : 2021/4/20 5:42 下午
     * @Author : < Jason.C >
     */
    public function paid(): void
    {
        $this->state   = self::STATUS_SUCCESS;
        $this->paid_at = now();
        $this->save();

        event(new Paid($this));
    }

    /**
     * Notes   : 根据支付订单，获取支付渠道需要的接口数据
     *
     * @Date   : 2021/4/21 4:24 下午
     * @Author : < Jason.C >
     * @param  string  $title  订单标题
     * @param  array  $extends  扩展数据， 微信公众号  ['openid' => OPENID]
     * @return mixed
     * @throws \Exception
     */
    public function getPaymentParams(string $title, array $extends = [])
    {
        $order['out_trade_no'] = $this->trade_id;

        $driver  = $this->driver;
        $gateway = $this->gateway;

        if ($driver === self::DRIVER_WECHAT) {
            $order['out_trade_no'] = $this->trade_id;
            if (config('payment.version', 2) == 2) {
                $order['body']      = $title;
                $order['total_fee'] = $this->total * 100;
            } else {
                $order['description']     = $title;
                $order['amount']['total'] = $this->total * 100;
            }
        } elseif ($driver === self::DRIVER_ALIPAY) {
            $order['total_amount'] = $this->total;
            $order['out_trade_no'] = $this->trade_id;
            $order['subject']      = $title;
        } else {
            throw new Exception('unsupported driver');
        }

        if (! empty($extends)) {
            $order = array_merge($order, $extends);
        }

        return Pay::$driver()->$gateway($order);
    }

}
