<?php

namespace Service;

use Event\Event;
use Lib\Imap\Fun;
use Lib\Imap\Imap;
use Lib\Imap\ImapConfig;
use Lib\Imap\ImapPool;
use Lib\Imap\Parse\Folder\Folder;
use Lib\Imap\Parse\MessageItem;
use Model\bodySql;
use Model\emailSql;
use Model\folderSql;
use Model\listsSql;

/**
 * 同步邮件
 * @author:dc
 * @time 2024/9/26 9:31
 * Class SyncMail
 * @package Service
 */
class SyncMail {

    /**
     * @var \Lib\Db|\Lib\DbPool
     */
    protected $db;

    /**
     * @var \Lib\Imap\Imap
     */
    protected $imap;

    /**
     * @var array
     */
    protected $email;


    protected $isStop = false;

    /**
     * SyncMail constructor.
     * @param int|string|array $email
     * @throws \Exception
     */
    public function __construct(int|string|array $email,\Lib\Imap\Imap|null $imap = null)
    {
        $this->db = db();

        if(!is_array($email)){
            $email = $this->db->cache(3600)->first(emailSql::first($email));
            if(!$email){
                abort('未查询到邮箱');
            }
        }
        $this->email = $email;
        // 实例一个imap类
        if($imap instanceof \Lib\Imap\Imap){
            $this->imap = $imap;
        }else{
            $this->imap = new Imap(
                (new ImapConfig())
                    ->setHost($email['imap'])
                    ->setEmail($email['email'])
                    ->setPassword(base64_decode($email['password']))
//                ->debug()
            );

            $this->login();

        }
    }

    public function stop(){
        $this->isStop = true;
    }


    protected function emailId(){
        return $this->email['id'];
    }

    /**
     * 登录imap
     * @throws \Exception
     * @author:dc
     * @time 2024/9/26 9:58
     */
    private function login(){
        $login = $this->imap->login();
        if(!$login->isOk()){
            foreach ([
                         '[ALERT] Invalid credentials (Failure)',// 登录失败
                         '[AUTHENTICATIONFAILED] Invalid credentials (Failure)',// 登录失败
                         '[AUTHENTICATIONFAILED] Authentication failed.',// 登录失败 权限
                         'LOGIN Login error',// 登录失败
                         'LOGIN auth error',// 登录失败
                         'ERR.LOGIN.PASSERR',// 登录失败 密码错误
                         'Login fail.',// 登录失败
                         'LOGIN failed.', // 登录失败
//                    'NO ERR.LOGIN.REQCODE', // 未知错误
                         '[ALERT] Application-specific password', // 这个错误是没有提供特定的授权码
                         'LOGIN Login error, user name or password error'
                     ] as $em){
                if(str_contains($login->getMessage(), $em)){
                    $this->db->update(
                        \Model\emailSql::$table,
                        ['pwd_error'=>1],
                        dbWhere(['id'=> $this->emailId()])
                    );
                }
            }

            abort($login->getMessage()?:'连接服务器异常');
        }
    }

    /**
     * @param $folder
     * @param int $pid
     * @return array
     * @throws \Exception
     * @author:dc
     * @time 2024/9/26 10:46
     */
    protected function folder($folder,$pid = 0){
        $uuids = [];

        foreach ($folder as $item){
            /** @var Folder $item*/

            $uuid = md5($this->emailId().$item->folder);
            $uuids[$uuid] = $uuid;

            $folder_name = '';
            if($item->flags){
                // 有些邮箱是把公共的文件夹标记在flag里面的,识别出来
                foreach ($item->flags as $flag){
                    if(in_array($flag,['Send','Drafts','Junk','Trash'])){
                        $folder_name = folderAlias($flag);
                    }
                }
            }
            if(!$folder_name){
                $fn = explode('/',$item->getParseFolder());
                $folder_name = folderAlias(end($fn));
            }

            // 是否存在
            $id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));
            $data = [
                'email_id' => $this->emailId(),
                'folder' => $folder_name,
                'origin_folder' => $item->folder,
                'uuid'  =>  $uuid,
                'pid'   =>  $pid
            ];
            if ($id){
                $this->db->update(folderSql::$table,$data,dbWhere(['id'=>$id]),false);
            }else{
                $id = $this->db->insert(folderSql::$table,$data,false);
                if(!$id) abort('文件夹写入异常 '.json_encode($data,JSON_UNESCAPED_UNICODE));
            }
            // 是否有子级目录
            if($id && $item->getChild()){
                $uuids = array_merge($uuids,$this->folder($item->getChild(),$id));
            }
        }

        return $uuids;
    }

    /**
     * @param bool $syncMail
     * @return bool|void
     * @throws \Exception
     * @author:dc
     * @time 2024/10/18 17:53
     */
    public function sync($syncMail = true){
        $this->isStop = false;
        /*********************************** 同步文件夹 ***************************************/
        // 获取文件夹
        $folders = $this->imap->getFolders();
        $uuids = $this->folder($folders->getTopFolder());
        if($uuids){
            // 删除以前的
            $this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);
        }

        if (!$syncMail) return true;

        _echo($this->emailId().' ===> 文件夹同步成功');
        if($this->isStop) return;


        /********************* 同步邮件 **********************/

        // 循环文件夹
        foreach ($folders->all() as $f){
            if($this->isStop) return;

            if($f->isSelect){ // 是否可以选择 只有可以选中的文件夹才有邮件
                $folder = $this->imap->folder($f); // 选择文件夹后,有状态

                // 是否有邮件 有邮件才继续
                if ($folder->getTotal()){
                    $num = $this->mail($folder);
                    if($num){
                        _echo($this->emailId().' ===> '.$folder->getName().' ===> '.$num);
                    }
                }

                // 更新数量
//                $this->db->update(folderSql::$table,[
//                    'exsts' =>   $this->db->count(listsSql::listCount(
//                        dbWhere(
//                            [
//                                'folder_id'=>$this->getFolderId($folder->getName()),
//                                'deleted'  =>  0,
//                            ]
//                        )
//                    )),
//                    'unseen' =>   $this->db->count(listsSql::listCount(
//                        dbWhere(
//                            [
//                                'folder_id'=>$this->getFolderId($folder->getName()),
//                                'seen'  =>  0,
//                                'deleted'  =>  0,
//                            ]
//                        )
//                    )),
//                    'last_sync_time' => time()
//                ],dbWhere(['email_id'=>$this->emailId(),'uuid'=>md5($this->emailId().$folder->getName())]),false);

            }
        }


    }

    /**
     * 当前 目录的id
     * @param string $name
     * @return mixed|null
     * @author:dc
     * @time 2024/10/12 17:44
     */
    private function getFolderId(string $name){
        return $this->db->cache(120)->value(folderSql::first([
            'email_id'=>$this->emailId(),
            'uuid'  =>  md5($this->emailId().$name)
        ],'`id`'));
    }

    /**同步邮件
     *
     * @param string|\Lib\Imap\Request\Folder $folder
     * @param array $uids 固定的uid
     * @param false $isBody 是否同时同步body
     * @author:dc
     * @time 2024/9/26 11:10
     */
    public function mail(string|\Lib\Imap\Request\Folder $folder, array $uids = [],$isBody = false):int {
        $sync_number = 0;
        if(is_string($folder)){
            $folder = $this->imap->folder($folder)->exec();
        }

        $folder_id = $this->getFolderId($folder->getName());

        // 选择成功
        if($folder->isOk()){
            $msg = $folder->msg();
            if($uids){
                $this->saveMail($folder_id,$msg->uid($uids)->get()->all(),$isBody);
            }else{
                $p=1;
                while (1){
                    if($this->isStop) return $sync_number;
                    $uids = $msg->forPage($p)->getUids();
                    if($uids){
                        $p++;

                        foreach ($uids as $k=>$uid){
                            if($this->db->cache(86400*30,false)->value(listsSql::first(dbWhere(['email_id'=>$this->emailId(),'folder_id'=>$folder_id,'uid'=>$uid]),'count(*) as c'))){
                                unset($uids[$k]);
                            }
                        }

                        if(!$uids) continue;

                        $lists = $msg->uid($uids)->get()->all();
                        $sync_number += count($lists);
                        // 没有数据就跳出
                        if($lists){
                            $this->saveMail($folder_id,$lists,$isBody);
                        }
                    }else{
                        break;
                    }

                }
            }

        }

        return  $sync_number;
    }

    /**
     * 保存邮件列表
     * @param int $folder_id
     * @param MessageItem[] $lists
     * @param bool $isBody
     * @author:dc
     * @time 2024/9/29 15:14
     */
    protected function saveMail(int $folder_id, array $lists, bool $isBody=false){
        foreach ($lists as $item){

            $data   =   [
                'uid'   =>  $item->uid,
                'subject'   =>  mb_substr($item->header->getSubject(),0,1000),// 控制下,有的蛋疼,整tm多长
                'cc'    =>  $item->header->getCc(true),
                'bcc'    =>  $item->header->getBcc(true),
                'from'   =>  $item->header->getFrom()->email,
                'from_name'   =>  $item->header->getFrom()->name,
                'to'   =>  implode(',',array_column($item->header->getTo(true),'email')),
                'to_name'   =>  $item->header->getTo(true),
                // 这个是 邮件的时间 就是header里面带的 一般情况就是发件时间
//                'date'   =>  strtotime($item->header->getDate()),
                'udate'   =>  strtotime($item->date), // 有这个时间就够了,内部时间,就是收到邮件的时间
                'size'   =>  $item->size,
                'recent'   =>  $item->isRecent() ? 1 : 0,
                'seen'   =>  $item->isSeen() ? 1 : 0,
                'draft'   =>  $item->isDraft() ? 1 : 0,
                'flagged'   =>  $item->isFlagged() ? 1 : 0,
                'answered'   =>  $item->isAnswered() ? 1 : 0,
                'folder_id'   =>  $folder_id,
                'email_id'    =>  $this->emailId(),
                'is_file'  =>  $item->isAttachment() ? 1: 0 //是否附件
            ];

            $data['from'] = mb_substr($data['from'],0,120);

            // 不知道为什么 有些邮件标题有下划线,但是发件那边并没有添加下划线
            $data['subject'] = str_replace('_',' ',$data['subject']);

            // 查询是否存在
            $id = $this->db->value(listsSql::first(dbWhere([
                'email_id'=> $data['email_id'],
                'folder_id' =>  $data['folder_id'],
                'uid'   =>  $data['uid']
            ]),'`id`'));


            if(!$id){

                $id = $this->insert($data);
                if(!$id){
                    continue;
                }
                // 新邮件标记
                if($item->getFolderName() == 'INBOX')
                    redis()->incr('have_new_mail_'.$this->emailId(),120);
                // 执行事件
                $data['Aicc-Hot-Mail'] = $item->header->get('Aicc-Hot-Mail');

                Event::call('mail_sync_list',$id, $data);

            }else{
                $this->db->update(listsSql::$table,$data,dbWhere(['id'=> $id]));
            }


            // 是否同步body内容
            if($isBody && $item->body->getRaw()){

                $body = [
                    'lists_id'  =>  $id,
                    'text_html' =>  []
                ];


                $body['text_html'][] = [
                    'body'  =>  base64_encode($item->getBody()->getHtml() ? : $item->getBody()->getText()),
                    'type'  =>  'text/html',
                    'charset'   =>  'utf-8',
                    'encode'   =>  'base64',
                ];

                // 处理附件
                foreach ($item->getBody()->getAttachment() as $itemBody){
                    $tmp = [
                        'body'  =>  '',
                        'type'  =>  $itemBody->getFileType(),
                        'charset'   =>  'binary',
                        'encode'   =>  $itemBody->data->get('content-transfer-encoding'),
                        'name'   =>  $itemBody->getFilename(),
                        'filename'   =>  $itemBody->getFilename(),
                        'path'  =>  $itemBody->save(MAIL_ATTACHMENT_PATH)
                    ];
                    if(!$tmp){
                        throw  new \Exception('请检查附件是否有写入权限');
                    }
                    if($itemBody->getContentId()){
                        $tmp['content-id'] = $itemBody->getContentId();
                    }
                    if($itemBody->data->get('Content-Disposition')){
                        $tmp['content-disposition'] = $itemBody->data->get('Content-Disposition');
                    }
                    $tmp['signName'] = explode('/',$tmp['path']);
                    $tmp['signName'] = end($tmp['signName']);

                    $body['text_html'][] = $tmp;

                }

                if($this->db->count(bodySql::has($id))){
                    $this->db->update(bodySql::$table,$body,'`lists_id` = '.$id,false);
                }else{
                    $this->db->insert(bodySql::$table,$body,false);
                }

                // 更新描述
                $this->db->update(listsSql::$table,['description'=>mb_substr($item->getBody()->getText(),0,150)],dbWhere(['id'=> $id]));

            }


        }


    }

    /**
     * 查询数据 并重试
     * @param array $data
     * @param int $num
     * @return int
     * @author:dc
     * @time 2024/10/12 15:32
     */
    protected function insert(array $data, int $num = 0){
        if($num>2){
            return 0;
        }
        try {
            $id = $this->db->throw()->insert(listsSql::$table,$data);
        }catch (\Throwable $e){
            // 字符串编码异常
            if(stripos($e->getMessage(),'Incorrect string value:')!==false){
                // 编码异常的 字段
                preg_match("/for column '([a-z0-9_]{2,})' at/",$e->getMessage(),$filed);
                if(!empty($filed[1]) && isset($data[$filed[1]])){
                    // 进行编码转换 大概率会失败
                    $data[$filed[1]] = Fun::mb_convert_encoding($data[$filed[1]],'UTF-8');
                }

                $id = $this->insert($data,$num+1);

            }
            logs([$data,$e->getMessage()]);
        }

        return $id??0;
    }








    public function __destruct()
    {
        // TODO: Implement __destruct() method.
        ImapPool::release($this->imap);
        unset($this->imap);
    }


}