<?php
namespace EasyChat\Model;

use EasyChat\Library\AppClock;
use EasyChat\Library\Email\EmailEndpoint;
use EasyChat\Library\Email\EmailDeliverabilityAdvisor;
use EasyChat\Library\Email\EmailProviderCatalog;
use EasyChat\Library\Messenger\SecretBox;

final class EmailAccount extends Base {
    public function get(bool $with_secret = false): array {
        $q = $this->db->query("SELECT * FROM `{$this->prefix}messenger_channels` WHERE `provider`='email' ORDER BY `channel_id` ASC LIMIT 1");
        if (!$q || !$q->row) return array();
        $row = $q->row;
        $settings = json_decode((string)$row['settings_json'], true);
        $row = array_merge($row, is_array($settings) ? $settings : array());
        $state = $this->getState((int)$row['channel_id']);
        if ($state) {
            $row = array_merge($row, $state);
             
            $row['imap_cursor_initialized'] = !empty($state['imap_cursor_initialized']);
            $row['imap_last_uid'] = (int)($state['imap_last_uid'] ?? 0);
            $row['imap_uidvalidity'] = (int)($state['imap_uidvalidity'] ?? 0);
        }
        if ($with_secret) {
            $key = (string)(new Setting($this->registry))->get('chat_install_secret', '');
            $credentials = json_decode((string)$q->row['credentials_json'], true);
            if (is_array($credentials) && $key !== '') {
                $row['username'] = SecretBox::decrypt((string)($credentials['username'] ?? ''), $key);
                $row['password'] = SecretBox::decrypt((string)($credentials['password'] ?? ''), $key);
                $row['smtp_bz_api_key'] = SecretBox::decrypt((string)($credentials['smtp_bz_api_key'] ?? ''), $key);
            }
        }
        unset($row['credentials_json'], $row['settings_json']);
        return $row;
    }

    public function save(array $data): array {
        $prepared = $this->prepareConnection($data);
        $current = $prepared['current'];
        $key = (string)(new Setting($this->registry))->get('chat_install_secret', '');
        if ($key === '') throw new \RuntimeException('email_install_secret_missing');
        $credentials = json_encode(array(
            'username'=>SecretBox::encrypt($prepared['username'], $key),
            'password'=>SecretBox::encrypt($prepared['password'], $key),
            'smtp_bz_api_key'=>SecretBox::encrypt($prepared['smtp_bz_api_key'], $key),
        ), JSON_UNESCAPED_SLASHES);
        $settings = array(
            'email_address'=>$prepared['email_address'],
            'display_name'=>$prepared['display_name'],
            'provider_id'=>$prepared['provider_id'],
            'imap_host'=>$prepared['imap_host'],
            'imap_port'=>$prepared['imap_port'],
            'smtp_host'=>$prepared['smtp_host'],
            'smtp_port'=>$prepared['smtp_port'],
            'outbound_transport'=>$prepared['outbound_transport'],
            'sender_email'=>$prepared['sender_email'],
            'folder_name'=>'INBOX',
            'import_mode'=>'new_only',
            'reply_mode'=>'sender',
            'allow_private_server'=>$prepared['allow_private_server'],
            'hide_quotes'=>true,
            'collapse_signatures'=>true,
            'raw_retention_days'=>90,
        );
        $now = $this->db->escape((new AppClock($this->registry))->nowSql());
        $status = !empty($data['enabled']) ? 'active' : 'disabled';
        $id = (int)($current['channel_id'] ?? 0);
        $email = $prepared['email_address'];
        if ($id) {
            $this->db->query("UPDATE `{$this->prefix}messenger_channels` SET `external_account_id`='".$this->db->escape(hash('sha256',$email))."',`auth_type`='password',`title`='".$this->db->escape($email)."',`status`='{$status}',`credentials_json`='".$this->db->escape($credentials)."',`settings_json`='".$this->db->escape(json_encode($settings, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."',`last_error`='',`date_modified`='{$now}' WHERE `channel_id`={$id}");
        } else {
            $public = bin2hex(random_bytes(16));
            $this->db->query("INSERT INTO `{$this->prefix}messenger_channels` SET `provider`='email',`external_account_id`='".$this->db->escape(hash('sha256',$email))."',`auth_type`='password',`title`='".$this->db->escape($email)."',`status`='{$status}',`public_id`='{$public}',`credentials_json`='".$this->db->escape($credentials)."',`settings_json`='".$this->db->escape(json_encode($settings, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."',`date_added`='{$now}',`date_modified`='{$now}'");
            $id = (int)$this->db->getLastId();
        }
        $this->ensureState($id, $prepared['outbound_transport'], $this->fingerprint($prepared));
        return $this->get(false);
    }

     
    public function prepareConnection(array $data): array {
        $current = $this->get(true);
        $email = strtolower(trim((string)($data['email_address'] ?? ($current['email_address'] ?? ''))));
        if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 254) throw new \InvalidArgumentException('email_address_invalid');
        $providerId = strtolower(trim((string)($data['provider_id'] ?? ($current['provider_id'] ?? EmailProviderCatalog::MANUAL))));
        $preset = EmailProviderCatalog::find($providerId);
        if ($providerId !== EmailProviderCatalog::MANUAL && !$preset) throw new \InvalidArgumentException('email_provider_invalid');
        if ($preset) {
            $data['imap_host'] = $preset['imap_host'];
            $data['imap_port'] = $preset['imap_port'];
            $data['smtp_host'] = $preset['smtp_host'];
            $data['smtp_port'] = $preset['smtp_port'];
            $data['allow_private_server'] = false;
        }
        $allowPrivate = !empty($data['allow_private_server']);
        $data['imap_host'] = EmailEndpoint::normalizeHost((string)($data['imap_host'] ?? ''), $allowPrivate);
        $data['imap_port'] = EmailEndpoint::normalizePort($data['imap_port'] ?? 0, 'imap');
        if ($data['imap_host'] === '' || !$data['imap_port']) throw new \InvalidArgumentException('email_imap_endpoint_invalid');
        $outbound = strtolower(trim((string)($data['outbound_transport'] ?? ($current['outbound_transport'] ?? 'auto'))));
        if (!in_array($outbound, array('auto','direct_smtp','cms_mail','local_relay','smtp_bz_api'), true)) throw new \InvalidArgumentException('email_outbound_transport_invalid');
        $smtpHost = EmailEndpoint::normalizeHost((string)($data['smtp_host'] ?? ''), $allowPrivate);
        $smtpPort = EmailEndpoint::normalizePort($data['smtp_port'] ?? 0, 'smtp');
        if (in_array($outbound, array('auto','direct_smtp'), true) && ($smtpHost === '' || !$smtpPort)) throw new \InvalidArgumentException('email_smtp_endpoint_invalid');
        $username = trim((string)($data['username'] ?? ($current['username'] ?? $email)));
        $password = (string)($data['password'] ?? '');
        if ($password === '') $password = (string)($current['password'] ?? '');
        if ($username === '' || $password === '') throw new \InvalidArgumentException('email_credentials_required');
        $apiKey = trim((string)($data['smtp_bz_api_key'] ?? ''));
        if ($apiKey === '') $apiKey = (string)($current['smtp_bz_api_key'] ?? '');
        if ($outbound === 'smtp_bz_api' && $apiKey === '') throw new \InvalidArgumentException('email_smtp_bz_key_required');
        $sender = strtolower(trim((string)($data['sender_email'] ?? ($current['sender_email'] ?? $email))));
        if (!filter_var($sender, FILTER_VALIDATE_EMAIL) || strlen($sender) > 254) throw new \InvalidArgumentException('email_sender_invalid');
        $prepared = array(
            'current'=>$current,'email_address'=>$email,'provider_id'=>$providerId,
            'display_name'=>mb_substr(trim((string)($data['display_name'] ?? ($current['display_name'] ?? ''))),0,128),
            'imap_host'=>$data['imap_host'],'imap_port'=>$data['imap_port'],
            'smtp_host'=>$smtpHost,'smtp_port'=>$smtpPort,
            'outbound_transport'=>$outbound,'sender_email'=>$sender,
            'allow_private_server'=>$allowPrivate,'username'=>$username,'password'=>$password,
            'smtp_bz_api_key'=>$apiKey,'status'=>!empty($data['enabled'])?'active':'disabled',
        );
        $prepared['config_fingerprint'] = $this->fingerprint($prepared);
        return $prepared;
    }

    public function fingerprint(array $data): string {
        $parts = array();
        foreach (array('email_address','provider_id','username','password','imap_host','imap_port','smtp_host','smtp_port','outbound_transport','sender_email','smtp_bz_api_key','allow_private_server') as $key) $parts[$key] = (string)($data[$key] ?? '');
        return hash_hmac('sha256', json_encode($parts, JSON_UNESCAPED_SLASHES), (string)(new Setting($this->registry))->get('chat_install_secret', 'email'));
    }

    public function recordConnectionCheck(array $prepared, array $result): string {
        $checkId = bin2hex(random_bytes(16));
        $channelId = (int)($prepared['current']['channel_id'] ?? 0);
        $this->db->query("DELETE FROM `{$this->prefix}email_connection_checks` WHERE `expires_at`<NOW()");
        $this->db->query("INSERT INTO `{$this->prefix}email_connection_checks` SET `check_id`='".$this->db->escape($checkId)."',`channel_id`=".($channelId>0?$channelId:'NULL').",`config_fingerprint`='".$this->db->escape($prepared['config_fingerprint'])."',`activation_ready`=".(!empty($result['activation_ready'])?1:0).",`result_json`='".$this->db->escape(json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."',`expires_at`=DATE_ADD(NOW(),INTERVAL 30 MINUTE),`date_added`=NOW()");
        if ($channelId > 0) $this->recordHealth($channelId, $result, (string)($result['outbound']['transport'] ?? $prepared['outbound_transport']));
        return $checkId;
    }

    public function checkAllowsActivation(string $checkId, string $fingerprint): bool {
        if ($checkId === '' || $fingerprint === '') return false;
        $q=$this->db->query("SELECT `check_id` FROM `{$this->prefix}email_connection_checks` WHERE `check_id`='".$this->db->escape($checkId)."' AND `config_fingerprint`='".$this->db->escape($fingerprint)."' AND `activation_ready`=1 AND `expires_at`>=NOW() LIMIT 1");
        return $q && $q->num_rows > 0;
    }

    public function promoteConnectionCheck(string $checkId, int $channelId): void {
        if ($checkId === '' || $channelId <= 0) return;
        $q=$this->db->query("SELECT `result_json` FROM `{$this->prefix}email_connection_checks` WHERE `check_id`='".$this->db->escape($checkId)."' AND `activation_ready`=1 AND `expires_at`>=NOW() LIMIT 1");
        if(!$q||!$q->row)return;$result=json_decode((string)$q->row['result_json'],true);if(!is_array($result))return;
        $this->recordHealth($channelId,$result,(string)($result['outbound']['transport']??'auto'));
        $this->db->query("UPDATE `{$this->prefix}email_connection_checks` SET `channel_id`={$channelId} WHERE `check_id`='".$this->db->escape($checkId)."'");
    }

    public function updateCursor(int $lastUid,int $uidValidity): void {
        $current=$this->get(false);if(!$current)return;$id=(int)$current['channel_id'];
        $this->db->query("INSERT INTO `{$this->prefix}email_channel_state` SET `channel_id`={$id},`config_revision`=1,`imap_cursor_initialized`=1,`imap_last_uid`=".max(0,$lastUid).",`imap_uidvalidity`=".max(0,$uidValidity).",`selected_outbound_transport`='".$this->db->escape((string)($current['outbound_transport']??'direct_smtp'))."',`inbound_status`='ready',`inbound_code`='imap_sync_ready',`inbound_checked_at`=NOW(),`diagnostics_json`='{}',`date_modified`=NOW() ON DUPLICATE KEY UPDATE `imap_cursor_initialized`=1,`imap_last_uid`=VALUES(`imap_last_uid`),`imap_uidvalidity`=VALUES(`imap_uidvalidity`),`inbound_status`='ready',`inbound_code`='imap_sync_ready',`inbound_checked_at`=NOW(),`date_modified`=NOW()");
        $this->db->query("UPDATE `{$this->prefix}messenger_channels` SET `last_inbound_at`=NOW(),`last_error`='',`date_modified`=NOW() WHERE `channel_id`={$id}");
    }

     
    public function recordRuntimeHealth(string $section,bool $success,string $code='',string $transport=''): void {
        if(!in_array($section,array('inbound','outbound'),true))return;
        $current=$this->get(false);$id=(int)($current['channel_id']??0);if($id<=0)return;
        $safeCode=preg_replace('/[^a-z0-9_\-]/i','',$code);
        if($safeCode==='')$safeCode=$section==='inbound'?'imap_sync_ready':'email_send_ready';
        $state=$success?'ready':'error';$statusColumn=$section.'_status';$codeColumn=$section.'_code';$checkedColumn=$section.'_checked_at';
        $diagnostics=json_decode((string)($current['diagnostics_json']??''),true);if(!is_array($diagnostics))$diagnostics=array();
        $diagnostics[$section]=array('status'=>$state,'code'=>$safeCode,'message'=>$success?($section==='inbound'?'Письма принимаются.':'Ответы отправляются.'):'Последняя рабочая операция завершилась ошибкой. Повторите проверку подключения.');
        $transportSql=$transport!==''?",`selected_outbound_transport`='".$this->db->escape($transport)."'":'';
        $this->db->query("UPDATE `{$this->prefix}email_channel_state` SET `{$statusColumn}`='".$this->db->escape($state)."',`{$codeColumn}`='".$this->db->escape($safeCode)."',`{$checkedColumn}`=NOW(),`diagnostics_json`='".$this->db->escape(json_encode($diagnostics,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."'{$transportSql},`date_modified`=NOW() WHERE `channel_id`={$id}");
        $this->db->query("UPDATE `{$this->prefix}messenger_channels` SET `last_error`='".($success?'':$this->db->escape($safeCode))."',`date_modified`=NOW() WHERE `channel_id`={$id}");
    }

     
    public function refreshDeliverabilityIfDue(): void {
        $current=$this->get(true);$id=(int)($current['channel_id']??0);if($id<=0)return;
        $checkedAt=strtotime((string)($current['deliverability_checked_at']??''));if($checkedAt!==false&&$checkedAt>time()-86400)return;
        $delivery=(new EmailDeliverabilityAdvisor())->advise($current,array('transport'=>(string)($current['selected_outbound_transport']??$current['outbound_transport']??'auto')));
        $diagnostics=json_decode((string)($current['diagnostics_json']??''),true);if(!is_array($diagnostics))$diagnostics=array();$diagnostics['deliverability']=$delivery;
        $this->db->query("UPDATE `{$this->prefix}email_channel_state` SET `deliverability_status`='".$this->db->escape((string)($delivery['status']??'not_checked'))."',`deliverability_code`='".$this->db->escape((string)($delivery['code']??''))."',`deliverability_checked_at`=NOW(),`diagnostics_json`='".$this->db->escape(json_encode($diagnostics,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."',`date_modified`=NOW() WHERE `channel_id`={$id}");
    }

    private function getState(int $channelId): array {
        if ($channelId <= 0) return array();
        $q=$this->db->query("SELECT * FROM `{$this->prefix}email_channel_state` WHERE `channel_id`={$channelId} LIMIT 1");
        return $q && $q->row ? $q->row : array();
    }
    private function ensureState(int $channelId,string $transport,string $fingerprint): void {
        $revision=max(1,(int)hexdec(substr($fingerprint,0,7)));
        $this->db->query("INSERT INTO `{$this->prefix}email_channel_state` SET `channel_id`={$channelId},`config_revision`={$revision},`selected_outbound_transport`='".$this->db->escape($transport)."',`inbound_status`='stale',`outbound_status`='stale',`deliverability_status`='not_checked',`diagnostics_json`='{}',`date_modified`=NOW() ON DUPLICATE KEY UPDATE `config_revision`={$revision},`selected_outbound_transport`=VALUES(`selected_outbound_transport`),`date_modified`=NOW()");
    }
    private function recordHealth(int $channelId,array $result,string $transport): void {
        $in=(array)($result['inbound']??array());$out=(array)($result['outbound']??array());$delivery=(array)($result['deliverability']??array());
        $this->db->query("INSERT INTO `{$this->prefix}email_channel_state` SET `channel_id`={$channelId},`config_revision`=1,`selected_outbound_transport`='".$this->db->escape($transport)."',`inbound_status`='".$this->db->escape((string)($in['status']??'not_checked'))."',`inbound_code`='".$this->db->escape((string)($in['code']??''))."',`inbound_checked_at`=NOW(),`outbound_status`='".$this->db->escape((string)($out['status']??'not_checked'))."',`outbound_code`='".$this->db->escape((string)($out['code']??''))."',`outbound_checked_at`=NOW(),`deliverability_status`='".$this->db->escape((string)($delivery['status']??'not_checked'))."',`deliverability_code`='".$this->db->escape((string)($delivery['code']??''))."',`deliverability_checked_at`=NOW(),`diagnostics_json`='".$this->db->escape(json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))."',`date_modified`=NOW() ON DUPLICATE KEY UPDATE `selected_outbound_transport`=VALUES(`selected_outbound_transport`),`inbound_status`=VALUES(`inbound_status`),`inbound_code`=VALUES(`inbound_code`),`inbound_checked_at`=NOW(),`outbound_status`=VALUES(`outbound_status`),`outbound_code`=VALUES(`outbound_code`),`outbound_checked_at`=NOW(),`deliverability_status`=VALUES(`deliverability_status`),`deliverability_code`=VALUES(`deliverability_code`),`deliverability_checked_at`=NOW(),`diagnostics_json`=VALUES(`diagnostics_json`),`date_modified`=NOW()");
    }
}
