<?php

namespace Lib\Mail;

use Event\syncMail;
use Lib\DbPool;
use Model\bodySql;
use Model\folderSql;
use Model\listsSql;

/**
 * 操作邮件
 * @author:dc
 * @time 2023/2/5 10:10
 * Class MailFun
 * @package Helper\Mail
 */
class Mail {

    /**
     * imap服务器连接实例
     * @var Imap
     */
    public Imap $client;

    /**
     * @var string
     */
    private string $username;

    /**
     * @var string
     */
    private string $password;

    /**
     * @var string
     */
    private string $server;

    /**
     * Mail constructor.
     * @param string $email
     * @param string $password
     * @param string $imap
     */
    public function __construct(string $email,string $password,string $imap)
    {
        $this->username = $email;
        $this->password = $password;
        $this->server = $imap;

        $this->client = new Imap();
    }

    /**
     * 登录imap服务器
     * @param bool $pass_err
     * @return int
     * @author:dc
     * @time 2023/3/14 10:03
     */
    public function login($pass_err=true):int {

        // 处理url
        $host = MailFun::getHostPort($this->server,993,'ssl://');
        try {
            // 是否初始成功
            $this->client->login($host['host'].':'.$host['port'],$this->username,$this->password);
        }catch (\Throwable $e){
            logs($this->username.'===>'.$e->getMessage());
            if($pass_err){
                // 是否是密码错误
                foreach ([
                             'NO [ALERT] Invalid credentials (Failure)',// 登录失败
                             'NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)',// 登录失败
                             'NO [AUTHENTICATIONFAILED] Authentication failed.',// 登录失败 权限
                             'NO LOGIN Login error',// 登录失败
                             'NO LOGIN auth error',// 登录失败
                             'NO ERR.LOGIN.PASSERR',// 登录失败 密码错误
                             'NO Login fail.',// 登录失败
                             'NO LOGIN failed.', // 登录失败
//                    'NO ERR.LOGIN.REQCODE', // 未知错误
                             'NO [ALERT] Application-specific password', // 这个错误是没有提供特定的授权码
                             'NO LOGIN Login error, user name or password error'
                         ] as $em){
                    if(str_contains($e->getMessage(), $em)){
                        db()->update(
                            \Model\emailSql::$table,
                            ['pwd_error'=>1],
                            dbWhere(['email'=>$this->username])
                        );
                    }
                }
                // 一天中超过 3次失败说明密码错误了
//                if(redis()->incr('email_login_error:'.md5($this->username),86400) > 10){
                // 登录失败了 ,
//                    db()->update(\Model\emailSql::$table,['pwd_error'=>1],dbWhere(['email'=>$this->username]));
//                }
                return -1;
            }

            return $e->getCode() == 403 ? 0 : -1;

        }
//        redis()->delete('email_login_error:'.md5($this->username));

        return 1;
    }


    /**
     * 同步文件夹
     * @param int $email_id
     * @param DbPool|null $db
     * @return mixed
     * @author:dc
     * @time 2023/2/5 10:58
     */
    public function syncFolder($email_id,$db=null){
        $db = $db ? $db : db();
        // 读取所有文件夹,未解密
        $folders    =   $this->client->getFolder();

        foreach ($folders as $k=>$item){
            $pname = explode('/',$item['folder']);
            if(count($pname)>1){
                array_pop($pname);
                $pname = implode('/',$pname);
            }else{
                $pname = '';
            }

            $folders[$k]['pname'] = $pname;
        }

        $p = 0;
        $uuids = [];
        while ($folders){
            foreach ($folders as $fk=>$folder){
                $uuid = md5($email_id.$folder['folder']);
                $uuids[$uuid] = $uuid;
                // 查找/出现的次数
                if (substr_count($folder['folder'],'/') == $p){
// 查找pid
                    $pid = $db->value(folderSql::has(['uuid'=>md5($email_id.$folder['pname'])]));
                    $pid = $pid ? $pid : 0;
//                    try {
                        $folder_name = '';
                        // 已发送
                        if(in_array('Send',$folder['check'])){
                            $folder_name = folderAlias('Send');
                        }
                        // 草稿
                        elseif(in_array('Drafts',$folder['check'])){
                            $folder_name = folderAlias('Drafts');
                        }
                        // 垃圾
                        elseif(in_array('Junk',$folder['check'])){
                            $folder_name = folderAlias('Junk');
                        }
                        // 回收站
                        elseif(in_array('Trash',$folder['check'])){
                            $folder_name = folderAlias('Trash');
                        }

                        if(!$folder_name){
                            $fn = explode('/',$folder['parseFolder']);
                            $folder_name = folderAlias(end($fn));
                        }
                        if(!$db->count(folderSql::has(['uuid'=>$uuid]))){
                            $db->insert(folderSql::$table,[
                                'email_id' => $email_id,
                                'folder' => folderAlias($folder_name),
                                'origin_folder' => $folder['folder'],
                                'uuid'  =>  $uuid,
                                'pid'   =>  $pid
                            ],false);
                        }else{
                            $db->update(folderSql::$table,[
                                'email_id' => $email_id,
                                'folder' => folderAlias($folder_name),
                                'origin_folder' => $folder['folder'],
                                'uuid'  =>  $uuid,
                                'pid'   =>  $pid
                            ],dbWhere(['email_id' => $email_id,'uuid'  =>  $uuid]),false);
                        }
//                    }catch (\Throwable $e){
                        // 这里就不处理失败了
//                    }

                    unset($folders[$fk]);

                }
            }
            $p++;
        }

        if($uuids){
            // 删除以前的
            $db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$email_id]);
        }

    }


    /**
     * 同步邮件
     * @param $email_id
     * @param $folder_id
     * @param string $folder
     * @param null|DbPool $db
     * @return bool|array
     * @throws \Exception
     * @author:dc
     * @time 2023/2/18 9:54
     */
    public function syncMail($email_id,$folder_id,$folder='INBOX') {
        if(empty($folder)){
            return 0;
        }
//        _echo('正在同步文件夹:'.$folder);
        $db = db();
        // 选择文件夹
        try {
            $status =   $this->client->selectFolder($folder);
        }catch (\Throwable $e){
            return 0;
        }


        // 是否有邮件
        if (!is_array($status) || !isset($status['EXISTS']) || !$status['EXISTS']){
            return true;
        }

        // 更新数量
        $upFolderData = ['exsts'=>$status['EXISTS'],'last_sync_time' => time()];
        // 谷歌 不返未读数量 谢特
        if(isset($status['UNSEEN'])){
            $upFolderData['unseen'] = $status['UNSEEN'];
        }
        $db->update(
            folderSql::$table,
            $upFolderData,
            dbWhere(['id'=>$folder_id]),
            false
        );

        // 读取黑名单
        $blacklist = redis()->get('blacklist:'.$email_id);
        $blackFolder = '';
        if($blacklist){
            $blackFolder = $db->value(folderSql::originFolder($email_id,'垃圾箱'));
        }

        //
        $nu = 100;
        $msgno = 1;
        $success_uid = [];
        while (true){

            // 结束操作了
            if(redis()->get(SYNC_RUNNING_REDIS_KEY) == 'stop'){
                break;
            }

            // 是否结束了
            if($status['EXISTS'] < $msgno){
                break;
            }
            // 是否超过了最大数量
            $maxmsgno = ($msgno-1)+$nu;
            if($maxmsgno > $status['EXISTS']){
                $maxmsgno = $status['EXISTS'];
            }
            $uids = $this->client->fetch(range($msgno,$maxmsgno),'UID');
            if(!$uids){
                break;
            }


            $uids = array_column($uids,'UID');
            $existsUids = $db->all(listsSql::getUids($email_id,$folder_id,$uids));
            if($existsUids){
                $existsUids = array_column($existsUids,'uid');
                // 获取不存在数据库的uid
                $uids = array_diff($uids,$existsUids);
            }


            $msgno += $nu;

            // 开始同步
            if($uids){
                $this->syncUidEmail(
                    $uids,
                    $email_id,
                    $folder,
                    $folder_id,
                    $blacklist,
                    $blackFolder,
                    $db
                );
                $success_uid = array_merge($success_uid,$uids);
            }

        }

        // 更新数量
        if(!isset($status['UNSEEN'])){
            // 统计未读数量
            $unseen = $db->count(listsSql::listCount(dbWhere([
                'seen'  => 0,
                'deleted'  => 0,
                'email_id'  => $email_id,
                'folder_id'  => $folder_id,
            ])));
            $db->update(
                folderSql::$table,
                ['unseen' => $unseen],
                dbWhere(['id'=>$folder_id]),
                false
            );
        }


        return $success_uid;

    }

    /**
     * 同步邮件 只通过 uid获取
     * @param array $uids
     * @param $email_id
     * @param $folder
     * @param $folder_id
     * @param $blacklist
     * @param $blackFolder
     * @param \Lib\DbPool $db
     * @throws \Exception
     * @author:dc
     * @time 2023/8/2 15:35
     */
    public function syncUidEmail(array $uids,$email_id,$folder,$folder_id,$blacklist,$blackFolder,$db){
        $results = $this->client->fetchHeader($uids,true);

        if($results && is_array($results)){
            // 表示已存在新邮件
            if($folder == 'INBOX') redis()->incr('have_new_mail_'.$email_id,120);

            // 批量插入
            foreach ($results as $key=>$result){
                $header = $result['HEADER.FIELDS'];

                foreach ($result['FLAGS'] as $k=>$FLAG){
                    $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));
                }
                try {

                    foreach ($header as $k=>$item){
                        $header[strtolower($k)] = $item;
                    }

                    // 没有收件人
                    $header['to'] = MailFun::toOrFrom($header['to']??'');


                    $header['from'] = MailFun::toOrFrom($header['from']);
                    // 抄送 ,密送
                    $cc = [];
                    $bcc = [];
                    if($header['cc']??''){
                        $cc = MailFun::toOrFrom($header['cc']);
                    }
                    if($header['bcc']??''){
                        $bcc = MailFun::toOrFrom($header['bcc']);
                    }


                    $data   =   [
                        'uid'   =>  $result['UID'],
                        'subject'   =>  $header['subject']??($header['Subject']??($header['SUBJECT']??'')),
                        'cc'    =>  $cc,
                        'bcc'    =>  $bcc,
                        'from'   =>  $header['from'][0]['email']??'',
                        'from_name'   =>  $header['from'][0]['name']??'',
                        'to'   =>  $header['to']?implode(',',array_column($header['to'],'email')):'',
                        'to_name'   =>  json_encode($header['to']),
                        'date'   =>  strtotime(is_array($header['date']??'') ? $header['date'][0] : $header['date']??''),
//                        'message_id'   =>  $header['message-id']??'',
                        'udate'   =>  strtotime($result['INTERNALDATE']),
                        'size'   =>  $result['RFC822.SIZE']??0,
                        'recent'   =>  in_array('recent',$result['FLAGS']) ? 1 : 0,
                        'seen'   =>  in_array('seen',$result['FLAGS']) ? 1 : 0,
                        'draft'   =>  in_array('draft',$result['FLAGS']) ? 1 : 0,
                        'flagged'   =>  in_array('flagged',$result['FLAGS']) ? 1 : 0,
                        'answered'   =>  in_array('answered',$result['FLAGS']) ? 1 : 0,
                        'folder_id'   =>  $folder_id,
                        'email_id'    =>  $email_id,
                        'is_file'  =>  MailFun::isFile($result['BODYSTRUCTURE']??'') ? 1: 0 //是否附件
                    ];
                    $data['date'] = $data['date'] ? : 0;

                    // 验证是否存在黑名单中
                    if($blacklist && $blackFolder!=$folder){
                        // 邮箱是否在黑名单中
                        $isBlacklist = false;
                        if (!empty($blacklist['emails']) && is_array($blacklist['emails']) && in_array($data['from'],$blacklist['emails'])){
                            $isBlacklist = true;
                        }
                        // 域是否存在
                        if (!empty($blacklist['domain']) && is_array($blacklist['domain']) && in_array(explode('@',$data['from'])[1],$blacklist['domain'])){
                            $isBlacklist = true;
                        }

                        if($isBlacklist && $blackFolder){
                            // 移入垃圾箱
                            try {
                                $this->client->move([$result['UID']],$blackFolder);
                            }catch (\Throwable $e){
                                logs('移动邮件失败 '.$result['UID'].':'.$e->getMessage().$e->getTraceAsString());
                            }

                            continue;
                        }
                    }


                }catch (\Throwable $e){
                    logs(
                        '邮件解析失败:'.PHP_EOL.$e->getMessage().PHP_EOL.print_r($result,true),
                        LOG_PATH.'/imap/mail/'.$email_id.'/'.$result['UID'].'.log'
                    );
                    unset($results[$key]);
                    continue;
                }

                // 插入数据库
                // 主题太长了就截取掉
                $data['subject'] = mb_substr($data['subject'],0,3500);
                try {
                    $id = $db->throw()->insert(listsSql::$table,$data);
                    if($id){
                        try {
                            go(function ($id,$header,$data){
                                new syncMail($id,$header,$data);
                            },...[$id,$header,$data]);
                        }catch (\Throwable $e){
                            logs($e->getMessage());
                        }

                    }

                }catch (\Throwable $e){
                    // 插入失败,尝试更新
                    $db->update(listsSql::$table,$data,dbWhere([
                        'email_id'=> $data['email_id'],
                        'folder_id' =>  $data['folder_id'],
                        'uid'   =>  $data['uid']
                    ]));
                }


                $results[$key] = [];
            }
        }
    }


    /**
     * 同步 邮件 内容 body
     * @param $folder_name
     * @param $uid
     * @param $id
     * @param null $db
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2023/4/23 17:40
     */
    public function syncBody($folder_name, $uid , $id, $db=null):bool {
        if(empty($folder_name)){
            return 0;
        }
        $db = $db ? $db : db();
        // 选择文件夹
        $this->client->selectFolder($folder_name);

        $body = $this->client->fetchBody([$uid],MAIL_ATTACHMENT_PATH,true);

        $body = array_values($body);
        $body = $body[0]['RFC822.TEXT']??'';

        if(!empty($body)){
            $description = '';
            foreach ($body as $key=>$item){

                if(!empty($item['body'])){
                    // 过滤二进制
                    $item['body'] = preg_replace('/<0x[a-f\d]+>/','',$item['body']);
                    $body[$key]['body'] = base64_encode($item['body']);
                }

                if(!$description && in_array($item['type']??'',['text/html','text/plain'])){

                    if(!empty($item['charset'])){
                        $value = @iconv($item['charset'],'utf-8',$item['body']);
                        $value = $value ? $value : $item['body'];
                    }else{
                        $value = $item['body'];
                    }
                    $value = @html_entity_decode($value, ENT_COMPAT, 'UTF-8');
                    $value=preg_replace("/<(script.*?)>(.*?)<(\/script.*?)>/si","",$value); //过滤script标签
                    $value=preg_replace("/<(\/?script.*?)>/si","",$value); //过滤script标签
                    $value=preg_replace("/javascript/si","Javascript",$value); //过滤script标签
                    $value=preg_replace("/<(style.*?)>(.*?)<(\/style.*?)>/si","",$value); //过滤style标签
                    $value=preg_replace("/<(\/?style.*?)>/si","",$value); //过滤style标签

                    $value = strip_tags($value);
                    $value = str_replace(["\n","\\n","&nbsp;"],'',$value);
                    $description = mb_substr(trim($value),0,190);

                }



                if(!empty($body[$key]['filename'])){
                    $body[$key]['filename'] = base64_encode($body[$key]['filename']);
                }

                if(!empty($body[$key]['name'])){
                    $body[$key]['name'] = base64_encode($body[$key]['name']);
                }

            }

            bodySql::insertOrUpdate([
                'lists_id'    =>  $id,
                'text_html'  =>  $body // todo::因为邮件会出现多编码问题,会导致数据库写不进去
            ]);


            // 更新描述
            try {
                $db->update(listsSql::$table,[
                    'description'  =>  @base64_encode($description) ? $description : '',
                    'is_file'   =>  MailFun::isBodyFile($body)
                ],dbWhere([
                    'id'    =>  $id
                ]));
            }catch (\Throwable $e){
                $db->update(listsSql::$table,[
                    'is_file'   =>  MailFun::isBodyFile($body)
                ],dbWhere([
                    'id'    =>  $id
                ]));
            }

        }

        return true;

    }


    /**
     * 设置为未读
     * @param $uids
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2022/10/26 17:11
     */
    public function seen($uids,$folder,$seen):bool{
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->flags($uids,[Imap::FLAGS_SEEN],$seen ? '+' : '-',true);
    }

    /**
     * 删除标记
     * @param $uids
     * @param $folder
     * @param $del
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2024/3/9 16:50
     */
    public function deleted($uids,$folder,$del=true):bool{
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->flags($uids,[Imap::FLAGS_DELETED],$del ? '+' : '-',true);
    }

    /**
     * 回复标记
     * @param $uids
     * @param $folder
     * @param $seen
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2023/4/6 17:10
     */
    public function answered($uids,$folder,$seen):bool{
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->flags($uids,[Imap::FLAGS_ANSWERED],$seen ? '+' : '-',true);
    }

    /**
     * 回复标记
     * @param $uids
     * @param $folder
     * @param $flagged
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2023/4/6 17:10
     */
    public function flagged($uids,$folder,$flagged):bool{
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->flags($uids,[Imap::FLAGS_FLAGGED],$flagged ? '+' : '-',true);
    }


    /**
     * 复制
     * @param $uids
     * @param $folder
     * @param $to_folder
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2023/3/22 16:38
     */
    public function copy($uids,$folder,$to_folder){
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->copy($uids,$to_folder);

    }

    /**
     * 移动邮件
     * @param $uids
     * @param $folder
     * @param $to_folder
     * @return bool
     * @throws \Exception
     * @author:dc
     * @time 2023/3/22 18:06
     */
    public function move($uids,$folder,$to_folder){
        // 选择目录
        $status =   $this->client->selectFolder($folder);

        return $this->client->move($uids,$to_folder);

    }

    /**
     * 清空标记为已删除的邮件,不可还原邮件
     * @author:dc
     * @time 2024/3/14 14:11
     */
    public function expunge(){
        return $this->client->expunge();
    }


//    /**
//     * 删除
//     * @param $uids
//     * @param $folder
//     * @return bool
//     * @throws \Exception
//     * @author:dc
//     * @time 2023/3/22 17:52
//     */
//    public function delete($uids,$folder){
//        // 选择目录
//        $status =   $this->client->selectFolder($folder);
//
//        return $this->client->delete($uids);
//    }




}