<?php
namespace EasyChat\Library;

use EasyChat\Model\Customer;

class CustomerMergeService {
    private $registry;
    private $db;
    private $prefix;
    private $detector;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->prefix = easychat_db_prefix();
        $this->detector = new CustomerProfileDetector();
    }

    public function profileConflictForChat(int $chat_id, string $url): array {
        $normalized = $this->detector->normalizeUrl($url);
        if (!$normalized) {
            return [];
        }

        $target_customer_id = (new Customer($this->registry))->ensureCustomerForChat($chat_id);
        if ($target_customer_id <= 0) {
            return [];
        }

        $profile = $this->findProfileByProviderHash($normalized['provider'], $normalized['profile_url_hash']);
        if (!$profile) {
            return [];
        }

        return $this->profileConflictPayload($target_customer_id, $profile, $normalized['profile_url']);
    }

    public function profileConflictPayload(int $target_customer_id, array $profile, string $profile_url = ''): array {
        $source_customer_id = $this->resolveMergedCustomerId((int)($profile['customer_id'] ?? 0));
        $target_customer_id = $this->resolveMergedCustomerId($target_customer_id);
        if ($source_customer_id <= 0 || $target_customer_id <= 0 || $source_customer_id === $target_customer_id) {
            return [];
        }

        return [
            'type' => 'profile_already_linked',
            'target_customer_id' => $target_customer_id,
            'source_customer_id' => $source_customer_id,
            'source_customer' => $this->customerPreview($source_customer_id),
            'profile' => $this->profilePayload($profile),
            'profile_url' => $profile_url !== '' ? $profile_url : (string)($profile['profile_url'] ?? ''),
            'merge_preview' => $this->mergePreview($target_customer_id, $source_customer_id, $profile_url),
        ];
    }

    public function mergePreview(int $target_customer_id, int $source_customer_id, string $profile_url = ''): array {
        $target_customer_id = $this->resolveMergedCustomerId($target_customer_id);
        $source_customer_id = $this->resolveMergedCustomerId($source_customer_id);
        if ($target_customer_id <= 0 || $source_customer_id <= 0 || $target_customer_id === $source_customer_id) {
            return [];
        }

        return [
            'target_customer' => $this->customerPreview($target_customer_id),
            'source_customer' => $this->customerPreview($source_customer_id),
            'source_customer_id' => $source_customer_id,
            'profile_url' => $profile_url,
            'counts' => [
                'dialogs' => $this->countRows('sessions', 'customer_id', $source_customer_id),
                'profiles' => $this->countRows('customer_profiles', 'customer_id', $source_customer_id),
                'notes' => $this->countRows('customer_notes', 'customer_id', $source_customer_id),
                'tags' => $this->countRows('customer_tag_links', 'customer_id', $source_customer_id),
                'contacts' => $this->countRows('customer_contact_points', 'customer_id', $source_customer_id),
            ],
        ];
    }

    public function mergeCustomersForChat(int $chat_id, int $source_customer_id, int $manager_id, string $reason = 'manual', string $profile_url = ''): array {
        $model_customer = new Customer($this->registry);
        $target_customer_id = $model_customer->ensureCustomerForChat($chat_id);
        if ($target_customer_id <= 0) {
            return ['success' => false, 'error' => 'chat_not_found'];
        }

        $target_customer_id = $this->resolveMergedCustomerId($target_customer_id);
        $source_customer_id = $this->resolveMergedCustomerId($source_customer_id);
        if ($source_customer_id <= 0) {
            return ['success' => false, 'error' => 'merge_source_not_found'];
        }
        if ($source_customer_id === $target_customer_id) {
            return ['success' => true, 'panel' => $model_customer->panelForChat($chat_id)];
        }

        $normalized_profile = null;
        if (trim($profile_url) !== '') {
            $normalized_profile = $this->detector->normalizeUrl($profile_url);
            if (!$normalized_profile) {
                return ['success' => false, 'error' => 'profile_url_invalid'];
            }
            $profile = $this->findProfileByProviderHash($normalized_profile['provider'], $normalized_profile['profile_url_hash']);
            if (!$profile) {
                return ['success' => false, 'error' => 'merge_profile_not_found'];
            }
            $profile_owner_id = $this->resolveMergedCustomerId((int)$profile['customer_id']);
            if ($profile_owner_id !== $source_customer_id && $profile_owner_id !== $target_customer_id) {
                return ['success' => false, 'error' => 'merge_profile_mismatch'];
            }
        }

        $target = $this->getCustomer($target_customer_id);
        $source = $this->getCustomer($source_customer_id);
        if (!$target || !$source) {
            return ['success' => false, 'error' => 'merge_customer_not_found'];
        }

        $reason = $this->normalizeReason($reason);
        $now = (new AppClock($this->registry))->nowSql();
        try {
            $this->db->query('START TRANSACTION');
            $this->lockCustomerRows([$target_customer_id, $source_customer_id]);
            $target = $this->getCustomer($target_customer_id);
            $source = $this->getCustomer($source_customer_id);
            if (!$target || !$source) {
                throw new \RuntimeException('customer_not_found');
            }
            $snapshot = $this->snapshot($target_customer_id, $source_customer_id);

            $this->moveContactPoints($target_customer_id, $source_customer_id, $now);

            $this->db->query(
                "UPDATE `{$this->prefix}sessions`
                 SET `customer_id` = " . (int)$target_customer_id . "
                 WHERE `customer_id` = " . (int)$source_customer_id
            );
            $this->db->query(
                "UPDATE `{$this->prefix}customer_notes`
                 SET `customer_id` = " . (int)$target_customer_id . "
                 WHERE `customer_id` = " . (int)$source_customer_id
            );
            $this->moveTagLinks($target_customer_id, $source_customer_id);
            $this->db->query(
                "UPDATE `{$this->prefix}customer_profiles`
                 SET `customer_id` = " . (int)$target_customer_id . ",
                     `date_modified` = '" . $this->db->escape($now) . "'
                 WHERE `customer_id` = " . (int)$source_customer_id
            );

            $updates = $this->mergedCustomerUpdates($target, $source, $now);
            if ($updates) {
                $this->db->query(
                    "UPDATE `{$this->prefix}customers`
                     SET " . implode(', ', $updates) . "
                     WHERE `customer_id` = " . (int)$target_customer_id
                );
            }

            $this->db->query(
                "UPDATE `{$this->prefix}customers`
                 SET `merged_into_customer_id` = " . (int)$target_customer_id . ",
                     `merged_at` = '" . $this->db->escape($now) . "',
                     `merged_by` = " . (int)$manager_id . ",
                     `date_modified` = '" . $this->db->escape($now) . "'
                 WHERE `customer_id` = " . (int)$source_customer_id
            );

            $this->db->query(
                "INSERT INTO `{$this->prefix}customer_merge_audit`
                 SET `target_customer_id` = " . (int)$target_customer_id . ",
                     `source_customer_id` = " . (int)$source_customer_id . ",
                     `manager_id` = " . (int)$manager_id . ",
                     `reason` = '" . $this->db->escape($reason) . "',
                     `profile_url` = '" . $this->db->escape($normalized_profile['profile_url'] ?? '') . "',
                     `snapshot_json` = '" . $this->db->escape(json_encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . "',
                     `date_added` = '" . $this->db->escape($now) . "'"
            );

            $this->db->query('COMMIT');
        } catch (\Throwable $e) {
            try {
                $this->db->query('ROLLBACK');
            } catch (\Throwable $rollback_error) {
                $this->logMergeFailure($rollback_error, [
                    'stage' => 'rollback',
                    'target_customer_id' => $target_customer_id,
                    'source_customer_id' => $source_customer_id,
                    'manager_id' => $manager_id,
                    'reason' => $reason,
                ]);
            }
            $this->logMergeFailure($e, [
                'stage' => 'merge',
                'target_customer_id' => $target_customer_id,
                'source_customer_id' => $source_customer_id,
                'manager_id' => $manager_id,
                'reason' => $reason,
                'profile_provider' => $normalized_profile['provider'] ?? '',
                'profile_hash' => $normalized_profile['profile_url_hash'] ?? '',
            ]);
            return ['success' => false, 'error' => 'merge_failed'];
        }

        return ['success' => true, 'panel' => $model_customer->panelForChat($chat_id)];
    }

    public function resolveMergedCustomerId(int $customer_id): int {
        $seen = [];
        while ($customer_id > 0 && !isset($seen[$customer_id])) {
            $seen[$customer_id] = true;
            $customer = $this->getCustomer($customer_id);
            if (!$customer) {
                return 0;
            }
            $next_id = (int)($customer['merged_into_customer_id'] ?? 0);
            if ($next_id <= 0) {
                return $customer_id;
            }
            $customer_id = $next_id;
        }
        return 0;
    }

    private function moveTagLinks(int $target_customer_id, int $source_customer_id): void {
        $this->db->query(
            "UPDATE `{$this->prefix}customer_tag_links` target_link
             JOIN (
                SELECT `chat_id`, `tag_id`, MIN(`date_added`) AS `date_added`
                FROM `{$this->prefix}customer_tag_links`
                WHERE `customer_id` = " . (int)$source_customer_id . "
                GROUP BY `chat_id`, `tag_id`
             ) source_link
                ON target_link.`customer_id` = " . (int)$target_customer_id . "
               AND target_link.`chat_id` = source_link.`chat_id`
               AND target_link.`tag_id` = source_link.`tag_id`
             SET target_link.`date_added` = LEAST(target_link.`date_added`, source_link.`date_added`)"
        );
        $this->db->query(
            "INSERT INTO `{$this->prefix}customer_tag_links`
                (`customer_id`, `chat_id`, `tag_id`, `date_added`)
             SELECT " . (int)$target_customer_id . ", source_link.`chat_id`, source_link.`tag_id`, MIN(source_link.`date_added`)
             FROM `{$this->prefix}customer_tag_links` source_link
             LEFT JOIN `{$this->prefix}customer_tag_links` target_link
                ON target_link.`customer_id` = " . (int)$target_customer_id . "
               AND target_link.`chat_id` = source_link.`chat_id`
               AND target_link.`tag_id` = source_link.`tag_id`
             WHERE source_link.`customer_id` = " . (int)$source_customer_id . "
               AND target_link.`id` IS NULL
             GROUP BY source_link.`chat_id`, source_link.`tag_id`"
        );
        $this->db->query(
            "DELETE FROM `{$this->prefix}customer_tag_links`
             WHERE `customer_id` = " . (int)$source_customer_id
        );
    }

    private function lockCustomerRows(array $customer_ids): void {
        $ids = [];
        foreach ($customer_ids as $customer_id) {
            $customer_id = (int)$customer_id;
            if ($customer_id > 0) {
                $ids[$customer_id] = $customer_id;
            }
        }
        if (!$ids) {
            return;
        }
        $this->db->query(
            "SELECT `customer_id`
             FROM `{$this->prefix}customers`
             WHERE `customer_id` IN (" . implode(',', array_values($ids)) . ")
             FOR UPDATE"
        );
    }

    private function moveContactPoints(int $target_customer_id, int $source_customer_id, string $now): void {
        $this->db->query(
            "UPDATE `{$this->prefix}customer_contact_points` target_cp
             JOIN `{$this->prefix}customer_contact_points` source_cp
                ON source_cp.`customer_id` = " . (int)$source_customer_id . "
               AND target_cp.`customer_id` = " . (int)$target_customer_id . "
               AND target_cp.`type` = source_cp.`type`
               AND target_cp.`value_hash` = source_cp.`value_hash`
             SET target_cp.`status` = " . $this->statusMergeSql('target_cp.`status`', 'source_cp.`status`') . ",
                 target_cp.`last_seen_at` = GREATEST(IFNULL(target_cp.`last_seen_at`, target_cp.`date_modified`), IFNULL(source_cp.`last_seen_at`, source_cp.`date_modified`)),
                 target_cp.`date_modified` = '" . $this->db->escape($now) . "'"
        );
        $this->db->query(
            "INSERT INTO `{$this->prefix}customer_contact_points`
                (`customer_id`, `type`, `value`, `value_hash`, `source`, `source_customer_id`,
                 `is_primary`, `status`, `source_chat_id`, `source_message_id`, `added_by`,
                 `date_added`, `date_modified`, `last_seen_at`)
             SELECT " . (int)$target_customer_id . ",
                    source_cp.`type`,
                    source_cp.`value`,
                    source_cp.`value_hash`,
                    source_cp.`source`,
                    source_cp.`source_customer_id`,
                    0,
                    source_cp.`status`,
                    source_cp.`source_chat_id`,
                    source_cp.`source_message_id`,
                    source_cp.`added_by`,
                    source_cp.`date_added`,
                    '" . $this->db->escape($now) . "',
                    IFNULL(source_cp.`last_seen_at`, source_cp.`date_modified`)
             FROM `{$this->prefix}customer_contact_points` source_cp
             LEFT JOIN `{$this->prefix}customer_contact_points` target_cp
                ON target_cp.`customer_id` = " . (int)$target_customer_id . "
               AND target_cp.`type` = source_cp.`type`
               AND target_cp.`value_hash` = source_cp.`value_hash`
             WHERE source_cp.`customer_id` = " . (int)$source_customer_id . "
               AND target_cp.`contact_point_id` IS NULL"
        );
        $this->db->query(
            "DELETE FROM `{$this->prefix}customer_contact_points`
             WHERE `customer_id` = " . (int)$source_customer_id
        );
        $this->ensurePrimaryContacts($target_customer_id);
    }

    private function logMergeFailure(\Throwable $e, array $context): void {
        try {
            (new Log('customer_merge'))->write([
                'message' => $e->getMessage(),
                'code' => $e->getCode(),
                'file' => basename($e->getFile()),
                'line' => $e->getLine(),
                'context' => $context,
            ]);
        } catch (\Throwable $ignored) {
             
        }
    }

    private function ensurePrimaryContacts(int $customer_id): void {
        foreach (['email', 'phone'] as $type) {
            $primary = $this->db->query(
                "SELECT `contact_point_id`
                 FROM `{$this->prefix}customer_contact_points`
                 WHERE `customer_id` = " . (int)$customer_id . "
                   AND `type` = '" . $this->db->escape($type) . "'
                   AND `is_primary` = 1
                   AND `status` <> 'archived'
                 LIMIT 1"
            );
            if ($primary && $primary->num_rows) {
                continue;
            }
            $candidate = $this->db->query(
                "SELECT `contact_point_id`
                 FROM `{$this->prefix}customer_contact_points`
                 WHERE `customer_id` = " . (int)$customer_id . "
                   AND `type` = '" . $this->db->escape($type) . "'
                   AND `status` <> 'archived'
                 ORDER BY FIELD(`status`, 'operator_confirmed', 'auto_detected', 'archived'),
                          `date_modified` DESC,
                          `contact_point_id` ASC
                 LIMIT 1"
            );
            if ($candidate && $candidate->num_rows) {
                $this->db->query(
                    "UPDATE `{$this->prefix}customer_contact_points`
                     SET `is_primary` = 1
                     WHERE `contact_point_id` = " . (int)$candidate->row['contact_point_id']
                );
            }
        }
    }

    private function mergedCustomerUpdates(array $target, array $source, string $now): array {
        $updates = [];
        $target_name = (string)($target['display_name'] ?? '');
        $source_name = (string)($source['display_name'] ?? '');
        if ($this->looksEmptyName($target_name) && !$this->looksEmptyName($source_name)) {
            $updates[] = "`display_name` = '" . $this->db->escape($this->cleanName($source_name)) . "'";
        }
        $updates[] = "`date_modified` = '" . $this->db->escape($now) . "'";
        return $updates;
    }

    private function snapshot(int $target_customer_id, int $source_customer_id): array {
        return [
            'target_customer' => $this->getCustomer($target_customer_id),
            'source_customer' => $this->getCustomer($source_customer_id),
            'source_profiles' => $this->rows("SELECT * FROM `{$this->prefix}customer_profiles` WHERE `customer_id` = " . (int)$source_customer_id . " LIMIT 200"),
            'source_contact_points' => $this->rows("SELECT * FROM `{$this->prefix}customer_contact_points` WHERE `customer_id` = " . (int)$source_customer_id . " LIMIT 200"),
            'counts' => $this->mergePreview($target_customer_id, $source_customer_id)['counts'] ?? [],
        ];
    }

    private function customerPreview(int $customer_id): array {
        $customer = $this->getCustomer($customer_id);
        if (!$customer) {
            return ['customer_id' => 0, 'display_name' => ''];
        }
        return [
            'customer_id' => (int)$customer['customer_id'],
            'display_name' => (string)$customer['display_name'],
            'date_modified' => (string)$customer['date_modified'],
            'primary_contacts' => (new CustomerContactService($this->registry))->primaryContactsForCustomer($customer_id),
        ];
    }

    private function profilePayload(array $profile): array {
        return [
            'profile_id' => (int)($profile['profile_id'] ?? 0),
            'provider' => (string)($profile['provider'] ?? ''),
            'provider_label' => $this->detector->providerLabel((string)($profile['provider'] ?? '')),
            'profile_url' => (string)($profile['profile_url'] ?? ''),
            'profile_url_hash' => (string)($profile['profile_url_hash'] ?? ''),
            'username' => (string)($profile['username'] ?? ''),
        ];
    }

    private function getCustomer(int $customer_id): array {
        $query = $this->db->query("SELECT * FROM `{$this->prefix}customers` WHERE `customer_id` = " . (int)$customer_id . " LIMIT 1");
        return $query->row ?: [];
    }

    private function findProfileByProviderHash(string $provider, string $hash): array {
        $query = $this->db->query(
            "SELECT *
             FROM `{$this->prefix}customer_profiles`
             WHERE `provider` = '" . $this->db->escape($provider) . "'
               AND `profile_url_hash` = '" . $this->db->escape($hash) . "'
             LIMIT 1"
        );
        return $query->row ?: [];
    }

    private function countRows(string $table, string $column, int $customer_id): int {
        $query = $this->db->query(
            "SELECT COUNT(*) AS total
             FROM `{$this->prefix}" . str_replace('`', '``', $table) . "`
             WHERE `" . str_replace('`', '``', $column) . "` = " . (int)$customer_id
        );
        return (int)($query->row['total'] ?? 0);
    }

    private function rows(string $sql): array {
        $query = $this->db->query($sql);
        return $query ? $query->rows : [];
    }

    private function normalizeReason(string $reason): string {
        $reason = strtolower(preg_replace('/[^a-z0-9_\-]/i', '', $reason));
        return in_array($reason, ['manual', 'profile_conflict', 'identity_related'], true) ? $reason : 'manual';
    }

    private function cleanName(string $value): string {
        $value = trim(strip_tags($value));
        $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value);
        return mb_substr((string)$value, 0, 255, 'UTF-8');
    }

    private function statusMergeSql(string $current, string $incoming): string {
        return "CASE
            WHEN {$current} = 'operator_confirmed' THEN {$current}
            WHEN {$incoming} = 'operator_confirmed' THEN {$incoming}
            WHEN {$current} = 'archived' AND {$incoming} <> 'archived' THEN {$incoming}
            ELSE {$current}
        END";
    }

    private function looksEmptyName(string $value): bool {
        $value = trim(mb_strtolower($value, 'UTF-8'));
        return $value === '' || $value === 'гость' || $value === 'guest' || $value === 'покупатель';
    }
}
