<?php
namespace EasyChat\Model;

use EasyChat\Library\AppClock;
use EasyChat\Library\Crm\Bitrix24Webhook;
use EasyChat\Library\Crm\AmoCrmDomain;
use EasyChat\Library\Crm\RetailCrmDomain;
use EasyChat\Library\Messenger\SecretBox;

final class CrmConnection extends Base {
    public function getPrimary(string $provider = 'bitrix24', bool $with_secret = false): array {
        $provider = strtolower(preg_replace('/[^a-z0-9_]/i', '', $provider));
        $query = $this->db->query("SELECT * FROM `{$this->prefix}crm_connections` WHERE `provider` = '" . $this->db->escape($provider) . "' ORDER BY `connection_id` ASC LIMIT 1");
        $row = $query && $query->row ? $query->row : array();
        if (!$row) return array();
        $settings = json_decode((string)($row['settings_json'] ?? ''), true);
        $row = array_merge($row, is_array($settings) ? $settings : array());
        unset($row['credentials_ciphertext'], $row['settings_json']);
        if ($with_secret) {
            $secret = (string)(new Setting($this->registry))->get('chat_install_secret', '');
            $plain = $secret !== '' ? SecretBox::decrypt((string)$query->row['credentials_ciphertext'], $secret) : '';
            $credentials = json_decode($plain, true);
            if (is_array($credentials)) $row = array_merge($row, $credentials);
        }
        return $row;
    }

    public function getById(int $connection_id, bool $with_secret = false): array {
        if ($connection_id <= 0) return array();
        $query = $this->db->query("SELECT * FROM `{$this->prefix}crm_connections` WHERE `connection_id`={$connection_id} LIMIT 1");
        if (!$query || !$query->row) return array();
        $row = $query->row;
        $settings = json_decode((string)($row['settings_json'] ?? ''), true);
        $row = array_merge($row, is_array($settings) ? $settings : array());
        unset($row['credentials_ciphertext'], $row['settings_json']);
        if ($with_secret) {
            $secret = (string)(new Setting($this->registry))->get('chat_install_secret', '');
            $plain = $secret !== '' ? SecretBox::decrypt((string)$query->row['credentials_ciphertext'], $secret) : '';
            $credentials = json_decode($plain, true);
            if (is_array($credentials)) $row = array_merge($row, $credentials);
        }
        return $row;
    }

    public function getActive(): array {
        $query = $this->db->query("SELECT `connection_id` FROM `{$this->prefix}crm_connections` WHERE `status`='active' ORDER BY `connection_id` ASC");
        $result = array();
        foreach ((array)($query->rows ?? array()) as $row) {
            $connection = $this->getById((int)$row['connection_id'], false);
            if ($connection) $result[] = $connection;
        }
        return $result;
    }

    public function saveBitrix24(array $data): array {
        $current = $this->getPrimary('bitrix24', true);
        $webhook = trim((string)($data['webhook_url'] ?? ''));
        if ($webhook === '') $webhook = (string)($current['webhook_url'] ?? '');
        $webhook = Bitrix24Webhook::normalize($webhook);
        if ($webhook === '') throw new \InvalidArgumentException('crm_webhook_invalid');
        $entity_mode = ($data['entity_mode'] ?? 'lead') === 'deal' ? 'deal' : 'lead';
        $status = !empty($data['enabled']) ? 'active' : 'disabled';
        $responsible_id = max(0, (int)($data['responsible_id'] ?? 0));
        $secret = (string)(new Setting($this->registry))->get('chat_install_secret', '');
        if ($secret === '') throw new \RuntimeException('crm_install_secret_missing');
        $cipher = SecretBox::encrypt(json_encode(array('webhook_url' => $webhook), JSON_UNESCAPED_SLASHES), $secret);
        $settings = json_encode(array('entity_mode' => $entity_mode, 'responsible_id' => $responsible_id), JSON_UNESCAPED_UNICODE);
        $now = $this->db->escape((new AppClock($this->registry))->nowSql());
        $id = (int)($current['connection_id'] ?? 0);
        if ($id > 0) {
            $this->db->query("UPDATE `{$this->prefix}crm_connections` SET `status`='{$status}', `credentials_ciphertext`='" . $this->db->escape($cipher) . "', `settings_json`='" . $this->db->escape($settings) . "', `last_error`='', `date_modified`='{$now}' WHERE `connection_id`={$id}");
        } else {
            $this->db->query("INSERT INTO `{$this->prefix}crm_connections` SET `provider`='bitrix24', `name`='Битрикс24', `status`='{$status}', `credentials_ciphertext`='" . $this->db->escape($cipher) . "', `settings_json`='" . $this->db->escape($settings) . "', `date_added`='{$now}', `date_modified`='{$now}'");
            $id = (int)$this->db->getLastId();
        }
        return $this->getPrimary('bitrix24', false);
    }

    public function saveAmoCrm(array $data): array {
        $current = $this->getPrimary('amocrm', true);
        $domain = AmoCrmDomain::normalize((string)($data['base_domain'] ?? ($current['base_domain'] ?? '')));
        $token = trim((string)($data['access_token'] ?? ''));
        if ($token === '') $token = (string)($current['access_token'] ?? '');
        if ($domain === '' || strlen($token) < 40 || strlen($token) > 8192 || preg_match('/\s/', $token)) throw new \InvalidArgumentException('amocrm_credentials_invalid');
        $pipeline_id = max(0, (int)($data['pipeline_id'] ?? 0));
        $status_id = max(0, (int)($data['status_id'] ?? 0));
        $responsible_id = max(0, (int)($data['responsible_id'] ?? 0));
        $enabled = !empty($data['enabled']);
        $mapping = isset($current['field_mapping']) && is_array($current['field_mapping']) ? $current['field_mapping'] : array();
        if (isset($data['field_mapping']) && is_array($data['field_mapping'])) $mapping = $data['field_mapping'];
        if ($enabled && ($pipeline_id <= 0 || $status_id <= 0 || empty($mapping['origin_field_id']) || empty($mapping['dialog_field_id']) || empty($mapping['source_field_id']))) throw new \InvalidArgumentException('amocrm_configuration_incomplete');
        $settings = array(
            'auth_type'=>'long_lived_token','base_domain'=>$domain,'pipeline_id'=>$pipeline_id,'status_id'=>$status_id,
            'responsible_id'=>$responsible_id,'field_mapping'=>$mapping,
            'trigger_digital_pipeline'=>!empty($data['trigger_digital_pipeline']),
        );
        return $this->saveConnection('amocrm', 'amoCRM', $enabled, array('access_token'=>$token), $settings);
    }

    public function updateAmoFieldMapping(array $mapping): array {
        $current = $this->getPrimary('amocrm', true);
        if (!$current) throw new \RuntimeException('amocrm_connection_missing');
        return $this->saveAmoCrm(array(
            'base_domain'=>$current['base_domain'],'access_token'=>$current['access_token'],'pipeline_id'=>(int)($current['pipeline_id'] ?? 0),
            'status_id'=>(int)($current['status_id'] ?? 0),'responsible_id'=>(int)($current['responsible_id'] ?? 0),
            'enabled'=>($current['status'] ?? '') === 'active','field_mapping'=>$mapping,
            'trigger_digital_pipeline'=>!empty($current['trigger_digital_pipeline']),
        ));
    }

    public function saveRetailCrm(array $data): array {
        $current = $this->getPrimary('retailcrm', true);
        $domain = RetailCrmDomain::normalize((string)($data['base_domain'] ?? ($current['base_domain'] ?? '')));
        $api_key = trim((string)($data['api_key'] ?? ''));
        if ($api_key === '') $api_key = (string)($current['api_key'] ?? '');
        if ($domain === '' || strlen($api_key) < 16 || strlen($api_key) > 512 || preg_match('/\s/', $api_key)) throw new \InvalidArgumentException('retailcrm_credentials_invalid');
        $mode = ($data['entity_mode'] ?? 'customer') === 'order' ? 'order' : 'customer';
        $site = $this->retailCode((string)($data['site'] ?? ($current['site'] ?? '')));
        if ($site === '') throw new \InvalidArgumentException('retailcrm_site_required');
        $settings = array(
            'auth_type'=>'api_key','base_domain'=>$domain,'entity_mode'=>$mode,'site'=>$site,
            'responsible_id'=>max(0, (int)($data['responsible_id'] ?? 0)),
            'order_method'=>$this->retailCode((string)($data['order_method'] ?? '')),
            'order_type'=>$this->retailCode((string)($data['order_type'] ?? '')),
            'order_status'=>$this->retailCode((string)($data['order_status'] ?? '')),
        );
        return $this->saveConnection('retailcrm', 'RetailCRM', !empty($data['enabled']), array('api_key'=>$api_key), $settings);
    }

    private function retailCode(string $value): string {
        $value = trim($value);
        return preg_match('/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/', $value) ? $value : '';
    }

    private function saveConnection(string $provider, string $name, bool $enabled, array $credentials, array $settings): array {
        $secret = (string)(new Setting($this->registry))->get('chat_install_secret', '');
        if ($secret === '') throw new \RuntimeException('crm_install_secret_missing');
        $cipher = SecretBox::encrypt(json_encode($credentials, JSON_UNESCAPED_SLASHES), $secret);
        $settings_json = json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $status = $enabled ? 'active' : 'disabled';
        $now = $this->db->escape((new AppClock($this->registry))->nowSql());
        $current = $this->getPrimary($provider, false);
        $id = (int)($current['connection_id'] ?? 0);
        if ($id > 0) {
            $this->db->query("UPDATE `{$this->prefix}crm_connections` SET `status`='{$status}',`credentials_ciphertext`='" . $this->db->escape($cipher) . "',`settings_json`='" . $this->db->escape($settings_json) . "',`last_error`='',`date_modified`='{$now}' WHERE `connection_id`={$id}");
        } else {
            $this->db->query("INSERT INTO `{$this->prefix}crm_connections` SET `provider`='" . $this->db->escape($provider) . "',`name`='" . $this->db->escape($name) . "',`status`='{$status}',`credentials_ciphertext`='" . $this->db->escape($cipher) . "',`settings_json`='" . $this->db->escape($settings_json) . "',`date_added`='{$now}',`date_modified`='{$now}'");
        }
        return $this->getPrimary($provider, false);
    }

    public function recordHealth(int $id, bool $success, string $error = ''): void {
        if ($id <= 0) return;
        $now = $this->db->escape((new AppClock($this->registry))->nowSql());
        $error = mb_substr(preg_replace('#https://[^\s]+#i', '[url hidden]', $error), 0, 255);
        $this->db->query("UPDATE `{$this->prefix}crm_connections` SET `last_check_at`='{$now}', `last_success_at`=" . ($success ? "'{$now}'" : '`last_success_at`') . ", `last_error`='" . $this->db->escape($error) . "', `date_modified`='{$now}' WHERE `connection_id`={$id}");
    }
}
