<?php

namespace App\Modules\Book\Services;

use App\Jobs\RunBookRentention;
use App\Modules\Book\Models\Book;
use App\Modules\Book\Models\BookCategory;
use App\Modules\Book\Models\BookConfig;
use App\Modules\Book\Models\NewBookTest;
use App\Modules\Book\Models\ReadRecord;
use App\Modules\Book\Models\RententionBookChapterUV;
use App\Modules\Book\Models\RententionBookList;
use App\Modules\Book\Models\RententionBookTaskList;
use Illuminate\Support\Collection;

/**
 * 留存书单
 */
class RententionBookService
{
    /**
     * 爆款待测试
     */
    const HotStyleWaittingTest  = 11;
    /**
     * 爆款待精修
     */
    const HotStyleWaittingRefine  = 12;
    /**
     * 爆款
     */
    const HotStyleOK  = 13;
    /**
     * 优质待测试
     */
    const HighQualityWaittingTest  = 21;
    /**
     * 优质待精修
     */
    const HighQualityWaittingRefine  = 22;
    /**
     * 优质
     */
    const HighQualityOK  = 23;
    /**
     * 不达标
     */
    const BelowStandard = 3;

    const new_status = 1;
    const ready_status = 2;
    const running_status = 3;
    const completed_status = 4;

    /**
     * 查找书单
     * @param string $book_name 书名
     * @param int $bid 书号
     * @param int $type 类型
     * @param bool $is_page 是否分页
     */
    public static function findBooksNew(array $params, bool $is_page = true)
    {
        $sql = RententionBookList::leftJoin('new_book_tests', 'new_book_tests.bid', 'rentention_book_list.bid')
            ->select('rentention_book_list.*', 'new_book_tests.type as test_type')
            ->where('rentention_book_list.is_deleted', 0);
        if ($params['book_name']) {
            $sql->where('rentention_book_list.book_name', 'like', $params['book_name'] . '%');
        }
        if ($params['bid']) {
            $sql->where('rentention_book_list.bid', $params['bid']);
        }
        if ($params['type']) {
            $sql->where('rentention_book_list.type', $params['type']);
        }
        if ($params['operate'] && in_array($params['operate'], ['>', '<', '=', '>=', '<=']) && is_numeric($params['arpu'])) {
            $sql->where('rentention_book_list.arpu', $params['operate'], $params['arpu']);
        }
        if ($is_page) {
            $lists = $sql->paginate();
            $collect = collect($lists->items());
        } else {
            $lists = $sql->get();
            $collect = $lists;
        }
        $bids = $collect->pluck('bid')->all();
        $books = Book::whereIn('id', $bids)->select('id', 'size', 'chapter_count')->get();
        foreach ($lists as $k => $v) {
            $book = $books->where('id', $v->bid)->first();
            $v->chapter_count = $book ? $book->chapter_count : 0;
            $v->size = $book ? $book->size : 0;
            $lists[$k] = $v;
        }
        return $lists;
    }

    /**
     * 删除书单中的书籍
     * @param int $bid
     */
    public static function deleteRetentionBook(int $bid)
    {
        RententionBookList::where('bid', $bid)->update([
            'is_deleted' => 1,
            'updated_at' => now(),
        ]);
    }

    /**
     * 书籍模板
     * @param int $bid
     * @return RententionBook|null
     */
    public static function bookTypeModel(int $bid)
    {
        $model = new HotStyle($bid);
        if (!$model->is_up_to_type) {
            unset($model);
            $model = new HighQuality($bid);
            if (!$model->is_up_to_type) {
                unset($model);
                $model = new BelowStandard($bid);
                if (!$model->is_up_to_type) {
                    return null;
                } else {
                    return $model;
                }
            } else {
                return $model;
            }
        } {
            return $model;
        }
    }

    /**
     * 书籍模板
     * @param int $bid
     * @return RententionBook
     */
    public static function getRententionModel(int $bid)
    {
        $model = self::bookTypeModel($bid);
        if (!$model) {
            $model = new RententionBook($bid);
        }
        return $model;
    }

    /**
     * 保存留存书籍数据
     */
    public static function saveRententionBook(int $bid, bool $is_force_update = false)
    {
        $model = self::bookTypeModel($bid);
        if ($model) {
            $model->saveRententionBook($is_force_update);
        } else {
            RententionBookList::where('bid', $bid)->update(['type' => 0]);
        }
    }

    /**
     * 跑书的留存
     */
    public static function runRentention(int $bid)
    {
        (new RententionBook($bid))->runRentention();
    }

    /**
     * 查找任务
     */
    public static function findRententionBookTasks(array $params, bool $is_page = true)
    {
        $sql = RententionBookTaskList::where('is_deleted', 0);
        if ($params['book_name']) {
            $sql->where('book_name', 'like', $params['book_name'] . '%');
        }
        if ($params['bid']) {
            $sql->where('bid', $params['bid']);
        }
        if ($is_page) {
            $lists = $sql->paginate();
            $collect = collect($lists->items());
        } else {
            $lists = $sql->get();
            $collect = $lists;
        }
        $bids = $collect->pluck('bid')->all();
        $books = Book::whereIn('id', $bids)->select('id', 'size', 'chapter_count')->get();
        $rentention_books = RententionBookList::whereIn('bid', $bids)->select('bid', 'type')->get();
        foreach ($lists as $k => $v) {
            $book = $books->where('id', $v->bid)->first();
            $rentention_book = $rentention_books->where('bid', $v->bid)->first();
            $v->chapter_count = $book ? $book->chapter_count : 0;
            $v->size = $book ? $book->size : 0;
            $v->type = $rentention_book ? $rentention_book->type : 0;
            $lists[$k] = $v;
        }
        return $lists;
    }

    /**
     * 添加书单任务
     */
    public static function addRententionBookTask(int $bid)
    {
        $book_config = BookConfig::where('bid', $bid)->select('book_name')->first();
        if ($book_config) {
            RententionBookTaskList::create([
                'bid' => $bid,
                'book_name' => $book_config->book_name,
                'status' => self::new_status
            ]);
        }
    }

    /**
     * 更新任务状态
     */
    public static function updateRententionBookTask(int $id, int $status)
    {
        $task = RententionBookTaskList::find($id);
        if ($task && $status != $task->status) {
            $task->status = $status;
            $task->save();
            if ($status == self::ready_status) {
                $job = new RunBookRentention($task->bid);
                dispatch($job)->onConnection('rabbitmq')->onQueue('run_book_rentention');
            }
        }
    }

    /**
     * 删除任务
     */
    public static function deleteRetentionBookTask(int $id)
    {
        RententionBookTaskList::where('id', $id)->update(['is_deleted' => 1, 'updated_at' => now()]);
    }
}

/**
 * 爆款书
 */
class HotStyle extends RententionBook
{
    const HotStyle = 1;

    const standard_rate = [
        30 => 0.0615,
        50 => 0.0436,
        110 => 0.0309,
        170 => 0.0207,
        230 => 0.0187,
        290 => 0.0155,
        350 => 0.0133,
        410 => 0.0113,
        470 => 0.0106,
        530 => 0.0105,
    ];

    const standard_levea_rate = [
        30 => 0.3,
    ];

    const standard_average_rate = [
        30 => 0.166,
        50 => 0.254,
        110 => 0.153,
        170 => 0.101,
        230 => 0.155,
        290 => 0.125,
        350 => 0.125,
        410 => 0.226,
        470 => 0.438,
    ];

    const arpu_level = 0.98;

    public function __construct(int $bid)
    {
        $this->standard_rate = self::standard_rate;
        $this->standard_levea_rate = self::standard_levea_rate;
        $this->standard_average_rate = self::standard_average_rate;
        $this->arpu_level = self::arpu_level;
        $this->model_type = self::HotStyle;
        parent::__construct($bid);
    }
}
/**
 * 优质书
 */
class HighQuality  extends RententionBook
{
    const HighQuality = 2;
    const standard_rate = [
        30 => 0.0532,
        50 => 0.0324,
        110 => 0.0224,
        170 => 0.0143,
        230 => 0.0133,
        290 => 0.0108,
        350 => 0.0078,
        410 => 0.0063,
        470 => 0.0050,
    ];

    const standard_levea_rate = [
        30 => 0.5,
    ];

    const standard_average_rate = [
        30 => 0.277,
        50 => 0.348,
        110 => 0.309,
        170 => 0.25,
        230 => 0.266,
        290 => 0.207,
        350 => 0.182,
        410 => 0.283,
        470 => 0.438,
    ];

    const arpu_level = 0.65;

    public function __construct(int $bid)
    {
        $this->standard_rate = self::standard_rate;
        $this->standard_levea_rate = self::standard_levea_rate;
        $this->standard_average_rate = self::standard_average_rate;
        $this->arpu_level = self::arpu_level;
        $this->model_type = self::HighQuality;
        parent::__construct($bid);
    }
}

/**
 * 不达标的书
 */
class BelowStandard extends RententionBook
{
    const BelowStandard = 3;
    public function __construct(int $bid)
    {
        parent::__construct($bid);
    }

    protected function judgeIsUpToType(array $rates)
    {
        $book = Book::where('id', $this->bid)->select('id', 'created_at', 'size')->first();
        $created_at = $book->created_at;
        $is_recently = time() <= strtotime('+1 month', strtotime($created_at));
        return $is_recently && ($book->size >= 1000000 || $this->findNewBookExists());
    }

    protected function judgeType()
    {
        return self::BelowStandard;
    }


    /**
     * 新书测试是否存在
     */
    private function findNewBookExists()
    {
        return NewBookTest::where('bid', $this->bid)->exists();
    }
}

/**
 * 留存书籍
 * @property Collection $uvs 章节留存UV;
 * @property array $rates 章节留存详情;
 * @property bool $is_up_to_type 是否符合利率类型;
 * @property float $arpu 新书测试的apru值;
 * @property int $type 书籍类型;
 */
class RententionBook
{
    const chapter_sequence = [
        30,
        50,
        110,
        170,
        230,
        290,
        350,
        410,
        470,
        530,
    ];

    const waitting_test = 1;
    const waitting_refine = 2;
    const ok = 3;

    /**
     * 标准留存率
     */
    protected $standard_rate;

    /**
     * 标准流失率
     */
    protected $standard_levea_rate;

    /**
     * 标准平均流失率
     */
    protected $standard_average_rate;

    /**
     * arpu标准值
     */
    protected $arpu_level;

    /**
     * 限制章节
     */
    protected $sequence_limit = 230;

    /**
     * 书籍类型
     */
    protected $model_type;

    protected $bid;

    public function __construct(int $bid)
    {
        $this->bid = $bid;
    }

    public function __get($name)
    {
        if (!isset($this->$name)) {
            switch ($name) {
                case 'uvs':
                    $this->$name = $this->findBookChapterUvs($this->bid);
                    break;
                case 'rates':
                    $this->$name = $this->findRententionRate($this->uvs);
                    break;
                case 'is_up_to_type':
                    if ($this->rates) {
                        $this->$name = $this->judgeIsUpToType($this->rates);
                    } else {
                        $this->$name = false;
                    }
                    break;
                case 'arpu':
                    $this->$name = $this->calcBookArpu();
                    break;
                case 'type':
                    $this->$name = $this->judgeType();
                    break;
            }
        }
        return $this->$name;
    }

    /**
     * 判断利率是否符合标准利率
     */
    private function judgeRateUpToStandard(Collection $rates, array $standard_rates, string $operate)
    {
        return $rates->where('sequence', '<=', $this->sequence_limit)
            ->every(function ($item) use ($standard_rates, $operate) {
                $sequence = $item['sequence'];
                if (isset($standard_rates[$sequence])) {
                    return $operate == '>' ? $item['rate'] >= $standard_rates[$sequence] : $item['rate'] <= $standard_rates[$sequence];
                } else {
                    return true;
                }
            });
    }

    /**
     * 计算留存率
     * @param int $first_chapter_uv 首章uv
     * @param int $sequence 章节序号
     * @param int $next_sequence 下一章章节序号
     */
    private function calcRententionRate(int $first_chapter_uv, int $sequence, int $next_sequence)
    {
        $model = $this->uvs->where('sequence', $sequence)->first();
        if ($model) {
            $uv = $model ? $model->uv : 0;
            $model = $this->uvs->where('sequence', $next_sequence)->first();
            $next_uv = $model ? $model->uv : 0;
            $chapter_rate = $uv / $first_chapter_uv;
            $chapter_leave_rate = 1 - $next_uv / $uv;
            $sequence_key = $sequence . '-' . $next_sequence;
            if ($next_sequence && $next_uv) {
                $period_uv = $this->uvs->where('sequence', '<=', $next_sequence)
                    ->where('sequence', '>', $sequence)
                    ->sum('uv');
                $chapter_average_rate = 1 - $period_uv / ($uv * ($next_sequence - $sequence));
            } else {
                $chapter_average_rate = 0;
            }
            return compact('sequence', 'chapter_rate', 'sequence_key', 'chapter_leave_rate', 'chapter_average_rate');
        }
    }

    /**
     * 计算新书测试的arpu值
     * @return float
     */
    private function calcBookArpu()
    {
        $book_test = NewBookTest::where('bid', $this->bid)
            ->select('uv_in_one_day', 'sub_amount_in_one_day')
            ->orderBy('id', 'desc')
            ->first();
        if ($book_test && $book_test->uv_in_one_day > 0) {
            return round($book_test->sub_amount_in_one_day / 100 / $book_test->uv_in_one_day, 2);
        } else {
            return 0;
        }
    }

    /**
     * 查找书籍留存uv
     * @param int $bid
     * @return  Collection
     */
    protected function findBookChapterUvs(int $bid)
    {
        $sequences = self::chapter_sequence;
        $sequence = end($sequences);
        return RententionBookChapterUV::where('bid', $bid)
            ->where('sequence', '<=', $sequence)
            ->select('sequence', 'uv')
            ->get();
    }

    /**
     * 查找书籍留存率
     * @param int $bid
     * @param Collection $uvs
     * @return array
     */
    protected function findRententionRate()
    {
        $rates = [];
        if (count($this->uvs) > 0) {
            $model = $this->uvs->where('sequence', 1)->first();
            $first_chapter_uv = $model ? $model->uv : 0;
            $next_sequence = 0;
            $sequences = self::chapter_sequence;
            foreach ($sequences as $k => $sequence) {
                $next_sequence = $k + 1 < count($sequences) ? $sequences[$k + 1] : 0;
                $item = $this->calcRententionRate($first_chapter_uv, $sequence, $next_sequence);
                if ($item) {
                    $rates[] = $item;
                } else {
                    break;
                }
            }
        }
        return $rates;
    }

    /**
     * 判断类型是否符合(只判断留存率和流失率)
     */
    protected function judgeIsUpToType(array $rates)
    {
        $rates = collect($rates);
        $chapter_rates = $rates->map(function ($item) {
            $data = [];
            $data['sequence'] = $item['sequence'];
            $data['rate'] = $item['chapter_rate'];
            return $data;
        });
        $leave_rates = $rates->map(function ($item) {
            $data = [];
            $data['sequence'] = $item['sequence'];
            $data['rate'] = $item['chapter_leave_rate'];
            return $data;
        });
        return $this->judgeRateUpToStandard($chapter_rates, $this->standard_rate, '>') &&
            $this->judgeRateUpToStandard($leave_rates, $this->standard_levea_rate, '<');
    }

    /**
     * 判断类型
     * @return int
     */
    protected function judgeType()
    {
        if ($this->is_up_to_type) {
            if ($this->arpu <= 0) {
                return (int) ($this->model_type . self::waitting_test);
            } else {
                if ($this->arpu >= $this->arpu_level) {
                    return (int) ($this->model_type . self::ok);
                } else {
                    return (int) ($this->model_type . self::waitting_refine);
                }
            }
        }
        return 0;
    }

    /**
     * 跑留存
     */
    public function runRentention()
    {
        $result = [];
        for ($i = 0; $i < 2048; $i++) {
            $records = $this->query(ReadRecord::model($i), $i);
            $result = array_merge_recursive($result, $records);
        }
        $this->saveChapterUV(collect($result));
    }

    /**
     * 查询章节uv数据
     */
    private function query(ReadRecord $readRecord, int $i)
    {
        return $readRecord->where('bid', $this->bid)
            ->whereIn('uid', function ($query) use ($i) {
                $query->select('uid')->from('record_records' . $i)->where('bid', $this->bid)->where('sequence', 1)->where('created_at', '<', date('Y-m-d H:i:s', strtotime('-1 day')));
            })
            ->groupBy('sequence')
            ->selectRaw('count(distinct uid) as uv, sequence')
            ->get()
            ->toArray();
    }

    /**
     * 保存章节uv
     */
    private function saveChapterUV(Collection $collection)
    {
        $groups = $collection->groupBy('sequence');
        $exists = RententionBookChapterUV::where('bid', $this->bid)->exists();
        if ($exists) {
            $groups->each(function ($item, $key) {
                RententionBookChapterUV::updateOrCreate([
                    'bid' => $this->bid,
                    'sequence' => $key,
                ], [
                    'uv' => $item->sum('uv'),
                ]);
            });
        } else {
            $data = $groups->map(function ($item, $key) {
                $data = [];
                $data['bid'] = $this->bid;
                $data['sequence'] = $key;
                $data['uv'] = $item->sum('uv');
                $data['created_at'] = now();
                return $data;
            });
            RententionBookChapterUV::insert($data->toArray());
        }
    }

    /**
     * 判断是否更新
     */
    private function judgeIsUpdated(int $chapter_count, int $size)
    {
        $first_uv = $this->uvs->where('sequence', 1)->first();
        return  $first_uv && $first_uv->uv > 2000 && ($chapter_count > 530 || $size > 1000000) ? 0 : 1;
    }

    /**
     * 保存留存书籍数据
     * @param bool $is_force_update 是否强制更新类型
     */
    public function saveRententionBook(bool $is_force_update)
    {
        $book_config = BookConfig::where('bid', $this->bid)->select('book_name', 'is_on_shelf')->first();
        $book = Book::where('id', $this->bid)->select('category_id', 'chapter_count', 'size')->first();
        if ($book) {
            $catetory = BookCategory::find($book->category_id);
        }
        $sex = $book && $catetory ? $catetory->pid : 0;
        $book_name = $book_config ? $book_config->book_name : '';
        if ($is_force_update) {
            return $this->updateBookType($book_name, $sex);
        } else {
            $is_updated = $this->judgeIsUpdated($book->chapter_count, $book->size);
            if (!$is_updated) {
                return $this->saveRententionBookList([
                    'type' => $this->type,
                    'is_updated' => 0,
                    'updated_time' => now(),
                ]);
            } else {
                return $this->updateBookType($book_name, $sex);
            }
        }
    }

    /**
     * 更新类型
     */
    private function updateBookType(string $book_name, int $sex)
    {
        return $this->saveRententionBookList([
            'arpu' => $this->arpu,
            'book_name' => $book_name,
            'type' => $this->type,
            'type_generate_time' => now(),
            'sex' => $sex,
        ]);
    }

    /**
     * 保存书单
     */
    public function saveRententionBookList(array $data)
    {
        RententionBookList::updateOrCreate([
            'bid' => $this->bid,
        ], $data);
    }
}