<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\console\input\Option;
use think\console\input\Argument;
use think\console\input\InputOption;
use Aria2;
use think\facade\Cache;
use think\facade\Log;
use GuzzleHttp\Client;

use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Strategy\Delegate\RequestMatcher;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage;
use Symfony\Component\Cache\Adapter\RedisAdapter;




class Zm extends Command
{
    private $aria2 = null;
    private $completed = false;
    private $client = null;
    private $user_id = '8ROYK0G3vGLyyEFjr9DtEQ==';

    protected function configure()
    {
        // 指令配置
        $this->setName('zm')
            ->setDescription('zm command');
    }

    protected function execute(Input $input, Output $output)
    {
        // 输出结果
        // $output->writeln("Hello, {$name}!");
        // $output->writeln("Option: {$option}");
        $this->aria2 = new Aria2('http://127.0.0.1:16800/jsonrpc','11223344'); //实例化aria2对象
        $this->aria2->setOption('enable-async-dns', true); //设置异步dns解析
        $this->aria2->setOption('enable-http-pipelining', true); //设置http管道化
        $this->aria2->setOption('enable-http-keep-alive', true); //设置http保持连接


    
        $cachePath = runtime_path() . 'guzzle_cache';
        $this->client = new Client([
            'verify' => false,
        ]);

        $this->completed = false;
        while($this->completed === false){
            try {
                $this->start();
            } catch (\Exception $e) {
                halt($e);
                sleep(3);
            }
        }
        dump('下载完成！');
    }
    protected function start()
    {
        $savepath = 'D:/zm';
        $lable = $this->getLable();
        if($lable['code'] == 0){
            $leftLabel = $lable['data']['leftLabel'];
            foreach ($leftLabel[0]['children'] as $key => $course) {
                $lable_title  = $course['title'];
                $lable_id     = $course['id'];
                $next = true;
                $curr_page = 1;
                $curr_total = 0;
                while($next){
                    $curr = $this->getCurr($lable_id,$curr_page);
                    $currs = $curr['data']['curriculum'];
                    foreach ($currs as $key => $value) {
                        $curr_total++;
                        $curr_item = $value;
                        $curr_item_title = $curr_item['title'];
                        $c = trim($savepath . '/' . $lable_title . '/' . $curr_item_title);
                        // dump('存储路径',$c);
                        try {
                            is_dir($c) || mkdir($c,0755,true);
                        } catch (\Exception $e) {
                            dump('创建目录失败：',$c,$e->getMessage());
                            throw $e;
                        }

                        $author = $c . '/' . '作者.txt';
                        $this->savetxt($author,$curr_item['author']);
                        
                        $introduce = $c . '/' . '介绍.txt';
                        $this->savetxt($introduce,$curr_item['introduce']);

                        $cover = $curr_item['cover'];
                        $this->download($cover,$c,basename($cover));

                        $cover_name = basename($cover);
                        $cover_path = "{$c}/{$cover_name}-下载地址.txt";
                        $this->savetxt($cover_path,$cover);
                        $curr_details = $this->getCurrDetails($curr_item['id']);
                        foreach($curr_details['data']['curriculum']['allClassSectionTrue'] as $class){
                            $class_path = trim($c . '/' . $class['title']);
                            try {
                                is_dir($class_path) || mkdir($class_path,0755,true);
                            } catch (\Exception $e) {
                                dump('创建目录失败：',$class_path,$e->getMessage());
                                throw $e;
                            }
                            $class_details = $this->getClassDetails($class['id']);
                            if($class_details == false){
                                dump('课程未授权，跳过下载',$class['title']);
                                break;
                            }
                            dump($class_path,$class_details);
                            $class_id = $class_details['data']['classSection']['id'];

                            if(!empty($class_details['data']['classSection']['video'])){
                                $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['video']);
                                $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt';
                                $this->savetxt($downloadurl,$class_details['data']['classSection']['video']);
                                $this->download($class_details['data']['classSection']['video'],$class_path,$file_name);
                            }
                            if(!empty($class_details['data']['classSection']['courseware'])){
                                $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['courseware']);
                                $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt';
                                $this->savetxt($downloadurl,$class_details['data']['classSection']['courseware']);
                                $this->download($class_details['data']['classSection']['courseware'],$class_path,$file_name);
                            }
                            if(!empty($class_details['data']['classSection']['construction_draw'])){
                                $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['construction_draw']);
                                $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt';
                                $this->savetxt($downloadurl,$class_details['data']['classSection']['construction_draw']);
                                $this->download($class_details['data']['classSection']['construction_draw'],$class_path,$file_name);
                            }
                            if(!empty($class_details['data']['classSection']['construction_draw_3d'])){
                                $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['construction_draw_3d']);
                                $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt';
                                $this->savetxt($downloadurl,urldecode($class_details['data']['classSection']['construction_draw_3d']));
                                $this->download($class_details['data']['classSection']['construction_draw_3d'],$class_path,$file_name);
                            }
                            
                            foreach($class_details['data']['classSection']['allEnclosure'] as $enclosure){
                                $enclosure['url'] = urldecode($enclosure['url']);
                                $parts = explode("{$class_id}/", $enclosure['url']);

                                $path = parse_url($enclosure['url'], PHP_URL_PATH);
                                $parts = explode('/', $path);
                                $storage = $class_path . '/' . urldecode($parts[count($parts)-2]);
                                is_dir($storage) || mkdir($storage,0755,true);
                                $downloadurl = $storage . '/' . urldecode(end($parts)) . '-下载地址.txt';
                                $this->savetxt($downloadurl,urldecode($enclosure['url']));
                                $this->download($enclosure['url'],$storage,basename($enclosure['url']));
                            }
                        }
                    }
                    $curr_page++;
                    if($curr_total >= $curr['data']['total']){
                        $next = false;
                        break;
                    }
                }
                // exit();
            }
            dump($leftLabel);
        }else{
            dump($lable['msg']);
            exit();
        }
        exit();
    }
    private function checkfile($url,$file)
    {
        dump($url,$file);
        if(is_file($file)){
            $size = filesize($file);
            if($size > 0){
                return true;
            }else{
                dump('文件大小为0，重新下载'.$file);
                unlink($file);
                return 2;
            }
        }
        $key = 'zm_check_header_' . md5($url);
        try {
            if(($head = Cache::get($key,null)) === NULL ){
                $head = [];

                try {
                    // 发送 HEAD 请求，并添加自定义请求头
                    $response = $this->client->head($url, [
                        'headers' => [
                            'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                            'referer'=>'https://edu.zmrobo.com/courses',
                        ],
                    ]);
                
                    // 读取响应状态码
                    $statusCode = $response->getStatusCode();
                    if($statusCode != 200){
                        dump('文件head请求失败，状态码：'.$statusCode);
                        return 0;
                    }
                    // 获取全部响应头（返回关联数组）
                    $headers = $response->getHeaders();
                    foreach ($headers as $name => $values) {
                        $head[$name] = implode(', ', $values);
                    }
                } catch (\GuzzleHttp\Exception\RequestException $e) {
                    dump("文件head请求失败: " . $e->getMessage());
                    return 0;
                }
                Cache::set($key,$head,86400);
            }
            $kk = 'zm_file_' . pathinfo($file,PATHINFO_BASENAME);
            if(isset($head['Content-Length']) && ($size = intval($head['Content-Length']))){
                $kk .= $size;
                if(is_file($file) && ($size !== filesize($file))){
                    dump('文件大小不符，重新下载'.$file);
                    unlink($file);
                    return 2;
                }
            }
            if(isset($head['ETag'])){
                $t = $kk . md5($head['ETag']);
                if(Cache::has($t)){
                    return Cache::get($t);
                }
            }
            if(isset($head['Content-MD5'])){
                $t = $kk . md5($head['Content-MD5']);
                if(Cache::has($t)){
                    return Cache::get($t);
                }
            }
        } catch (Exception $e) {
            dump('检查文件失败：',$url,$file,$e);
            exit;
        }
        return 3;
    }
    protected function download($downloadurl,$dir,$filename)
    {
        $result = $this->checkfile($downloadurl,$dir . '/' . $filename);
        
        if($result === true){
            dump('文件已存在，跳过下载：' . $filename);
            return;
        }
        $num = 1;
        while($num > 0){
            $activeDownloads = $this->aria2->getGlobalStat();
            $num = intval($activeDownloads['result']['numWaiting']);
            if($num > 50){
                dump('当前任务过多，等待任务数：',$num);
                sleep(1);
            }else{
                break;
            }
        }
        $result = $this->aria2->addUri(
            [$downloadurl],
            ['dir'=>$dir,
            'header'=>[
                'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                'referer: https://edu.zmrobo.com/courses',
            ],
            'out'=>$filename]
        );
    }
    public function getLable($sign = 'null')
    {
        $result = $this->client->get("https://edu.zmrobo.com/api/label",[
            'headers'=>[
                'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                'referer'=>'https://edu.zmrobo.com/courses',
                'E-Date'=>time(),
                'Sign'=>$sign,
            ]
        ]);
        $result = json_decode($result->getBody()->getContents(),true);
        if($result['code'] == -3){
            return $this->getLable($result['sign']);
        }else{

            return $result;
        }
    }
    public function getCurr($label_id,$page,$sign = 'null')
    {
        $cacheKey = 'zm_curr_' . $label_id . '_' . $page;
        if(Cache::has($cacheKey)){
            return Cache::get($cacheKey);
        }
        $options = [
            'headers'=>[
                'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                'referer'=>'https://edu.zmrobo.com/courses',
                'E-Date'=>time(),
                'Content-Type'=>'application/json; charset=utf-8',
                'Origin'=>'https://edu.zmrobo.com',
                'Sign'=>$sign,
            ],
            'body'=>json_encode([
                'label'=>[[$label_id]],
                'page'=>$page,
            ])
        ];
        $result = $this->client->post("https://edu.zmrobo.com/api/curriculum/list",$options);
        $result = json_decode($result->getBody()->getContents(),true);
        if($result['code'] == -3){
            return $this->getCurr($label_id,$page,$result['sign']);
        }else if($result['code'] == 0){
            Cache::set($cacheKey,$result,86400);
            return $result;
        }else{
            dump($result['msg']);
            exit();
        }
    }


    public function getCurrDetails($curr_id,$sign = 'null')
    {
        $cacheKey = 'zm_curr_details_' . $curr_id;
        if(Cache::has($cacheKey)){
            return Cache::get($cacheKey);
        }
        $options = [
            'headers'=>[
                'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                'referer'=>'https://edu.zmrobo.com/courses',
                'E-Date'=>time(),
                'Content-Type'=>'application/json; charset=utf-8',
                'Origin'=>'https://edu.zmrobo.com',
                'Sign'=>$sign,
            ],
            'body'=>json_encode([
                'id'=>$curr_id,
                'user_id'=>$this->user_id,
            ])
        ];
        $result = $this->client->post("https://edu.zmrobo.com/api/curriculum/details",$options);
        $result = json_decode($result->getBody()->getContents(),true);
        if($result['code'] == -3){
            return $this->getCurrDetails($curr_id,$result['sign']);
        }else if($result['code'] == 0){
            Cache::set($cacheKey,$result,86400);
            return $result;
        }else{
            dump($result['msg']);
            exit();
        }
    }
    public function getClassDetails($curr_id,$sign = 'null')
    {
        $cacheKey = 'zm_class_details_' . $curr_id;
        if(Cache::has($cacheKey)){
            return Cache::get($cacheKey);
        }
        $options = [
            'headers'=>[
                'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400',
                'referer'=>'https://edu.zmrobo.com/courses',
                'E-Date'=>time(),
                'Content-Type'=>'application/json',
                'Origin'=>'https://edu.zmrobo.com',
                'Sign'=>$sign,
            ],
            'body'=>json_encode([
                'id'=>$curr_id,
                'user_id'=>$this->user_id,
                'token'=>"7aa22a83fece93ed3e5a3119b085ced9",
            ])
        ];
        $result = $this->client->post("https://edu.zmrobo.com/api/class/details",$options);
        $result = json_decode($result->getBody()->getContents(),true);
        if($result['code'] == -3){
            return $this->getClassDetails($curr_id,$result['sign']);
        }else if($result['code'] == 0){
            Cache::set($cacheKey,$result,86400);
            return $result;
        }else if($result['code'] == -5){
            return false;
        }else{
            dump($result['msg']);
            exit();
        }
    }

    private function savetxt(string $file,string $content)
    {
        if(is_file($file)){
            unlink($file);
        }
        $myfile = fopen($file, "w");
        fwrite($myfile, $content);
        fclose($myfile);
        // dump('已保存文本内容：'.$file);
    }
    /**
     * 从URL中提取并解码文件名
     * @param string $url 包含文件名的URL
     * @param bool $throwExceptions 是否抛出异常 (默认true)
     * @return string|null 成功返回文件名，失败返回null(仅当$throwExceptions=false时)
     * @throws \InvalidArgumentException
     */
    function getFileNameFromUrl(string $url, bool $throwExceptions = true): ?string
    {
        // 解析URL结构
        $parsed = parse_url($url);
        
        // 错误检测逻辑
        if ($parsed === false || !isset($parsed['path'])) {
            $errorMsg = "无效的URL格式: " . json_encode($url);
            if ($throwExceptions) {
                throw new \InvalidArgumentException($errorMsg);
            }
            trigger_error($errorMsg, E_USER_WARNING);
            return null;
        }

        // 提取并解码文件名
        return urldecode(basename($parsed['path']));
    }
}