<?php

declare(strict_types=1);

namespace App\Service;

use App\Contract\SmsSender;
use App\Model\Sms;
use App\Exception\BusinessException;
use App\Helper\Log;

class SmsService extends Service
{
    const INTERVAL_MINUTE = 1;
    const IP_DAY_COUNT = 100;

    public function send($mobile, $content)
    {
        $smsSender = make(SmsSender::class);
        
        if (!$smsSender) {
            throw new BusinessException('系统异常,请联系客服');
        }
        $smsSender->send($mobile, $content);
    }

    public function sendBatch(array $mobile, $content)
    {
        $smsSender = make(SmsSender::class);
        
        if (!$smsSender) {
            throw new BusinessException('系统异常,请联系客服');
        }

        $smsSender->sendBatch($mobile, $content);
    }

    public function sendCode($mobile, array $options)
    {
        $clientIp = $options['client_ip'] ?? '';
        $smsSender = make(SmsSender::class);
        
        if (!$smsSender) {
            throw new BusinessException('系统异常,请联系客服');
        }
        if ($clientIp && $this->isIpOutOfDayCount($clientIp)) {
            throw new BusinessException('每天发送数量不能超过' . self::IP_DAY_COUNT . '条');
        }
        if ($this->isOutOfRate($mobile)) {
            throw new BusinessException('发送过于频繁,请稍后再试');
        }

        $code = $this->getCode();
        $sms = new Sms();
        $sms->code = $code;
        $sms->mobile = $mobile;
        $sms->client_ip = $clientIp;
        $sms->channel = $smsSender->channel;
        $sms->save();

        try {
            $smsSender->sendCode($mobile, $code, Sms::ACTIVE_MINUTE);
            $sms->status = Sms::STATUS_SUCCESS;
            $sms->result = 'success';
            $sms->save();
        } catch (\Exception $e) {
            $sms->status = Sms::STATUS_ERROR;
            $sms->result = 'error';
            $sms->save();
            Log::error(
                'SMS_SEND_ERROR: ' . $e->getMessage(),
                ['mobile' => $mobile, 'options' => $options],
                'sms'
            );
        }
    }

    public function check($mobile, $code): bool
    {
        $sms = Sms::where('mobile', $mobile)
            ->where('is_checked', 0)
            ->where('status', Sms::STATUS_SUCCESS)
            ->orderBy('created_at', 'desc')
            ->first();
        if (!$sms) {
            return false;
        }
        if ($sms->isExpired()) {
            return false;
        }
        if ($sms->code == $code) {
            $sms->is_checked = 1;
            $sms->save();
            return true;
        }
        return false;
    }

    private function getCode()
    {
        return rand(100000, 999999);
    }

    private function isOutOfRate($mobile): bool
    {
        $lastSms = Sms::select(['created_at'])->where(['mobile' => $mobile])->orderBy('created_at', 'desc')->first();
        if ($lastSms &&  strtotime($lastSms->created_at) + self::INTERVAL_MINUTE * 60 > time()) {
            return true;
        }
        return false;
    }

    private function isIpOutOfDayCount($ip): bool
    {
        $count = Sms::where('client_ip', $ip)
            ->where('status', Sms::STATUS_SUCCESS)
            ->whereBetween('created_at', [date('Y-m-d 00:00:00'), date('Y-m-d 23:59:59')])
            ->count();
        return $count > self::IP_DAY_COUNT;
    }
}