<?php
namespace App\Services;

use App\Repositories\PersonRepository;
use App\Repositories\VirtualStageRepository;
use App\Repositories\DealRepository;
use App\Repositories\PhoneChangeLogRepository;
use App\Repositories\ConfigRepository;
use App\Services\VirtualStageService;
use App\Services\DidarApiService;
use App\Services\NotificationService;
use App\Services\PhoneChangeLogService;
use App\Models\Deal;
use App\Utils\Sanitizer;
use App\Utils\Validator;
use App\Utils\PhoneNormalizer;
use App\Utils\Logger;

/**
 * Lead Service
 * Handles lead-related business logic
 */
class LeadService
{
    private $personRepo;
    private $dealRepo;
    private $didarApi;
    private ?NotificationService $notificationService;
    private ?CommissionService $commissionService;
    private $virtualStageRepo;
    private $virtualStageService;
    private $phoneChangeLogService;
    private ConfigRepository $configRepo;

    public function __construct(
        PersonRepository $personRepo,
        VirtualStageRepository $virtualStageRepo,
        DealRepository $dealRepo,
        VirtualStageService $virtualStageService,
        DidarApiService $didarApi,
        ConfigRepository $configRepo,
        ?NotificationService $notificationService = null,
        ?PhoneChangeLogService $phoneChangeLogService = null,
        ?CommissionService $commissionService = null
    ) {
        $this->personRepo = $personRepo;
        $this->virtualStageRepo = $virtualStageRepo; // kept for compatibility, not used
        $this->dealRepo = $dealRepo;
        $this->virtualStageService = $virtualStageService; // kept for compatibility
        $this->didarApi = $didarApi;
        $this->configRepo = $configRepo;
        $this->notificationService = $notificationService;
        $this->phoneChangeLogService = $phoneChangeLogService;
        $this->commissionService = $commissionService;
    }

    public function getPersonRepo()
    {
        return $this->personRepo;
    }

    private function isDidarWriteEnabled(): bool
    {
        return false;
    }

    public function deleteLeadCascade(string $contactId): array
    {
        $contactId = trim($contactId);
        if ($contactId === '') {
            throw new \Exception('شناسه لید الزامی است');
        }

        $person = $this->personRepo->findByDidarId($contactId);
        if (!$person) {
            throw new \Exception('لید یافت نشد');
        }

        $pdo = $this->personRepo->getDb()->getPdo();
        $pdo->beginTransaction();
        try {
            // Delete activities first (so activity_reminders cascade)
            $stmt = $pdo->prepare("DELETE FROM activities WHERE contact_didar_id = ?");
            $stmt->execute([$contactId]);
            $deletedActivities = (int)$stmt->rowCount();

            // Delete deals (deal_products cascade; activities.deal_didar_id is ON DELETE SET NULL but we already removed activities)
            $stmt = $pdo->prepare("DELETE FROM deals WHERE contact_didar_id = ?");
            $stmt->execute([$contactId]);
            $deletedDeals = (int)$stmt->rowCount();

            // Delete person (virtual_stages/referrals/customer_satisfactions cascade)
            $this->personRepo->deleteByDidarId($contactId);

            $pdo->commit();
            return [
                'deleted_person' => 1,
                'deleted_deals' => $deletedDeals,
                'deleted_activities' => $deletedActivities,
            ];
        } catch (\Throwable $e) {
            $pdo->rollBack();
            Logger::logError('Failed to delete lead cascade', $e, ['contact_id' => $contactId]);
            throw $e;
        }
    }

    /**
     * Get leads with filters
     */
    public function getLeads($filters = [])
    {
        $leads = $this->personRepo->getLeadsWithVirtualStage($filters);

        foreach ($leads as &$lead) {
            if (array_key_exists('has_previous_purchase', $lead) && $lead['has_previous_purchase'] !== null && $lead['has_previous_purchase'] !== '') {
                $lead['has_previous_purchase'] = (int)$lead['has_previous_purchase'];
            } else {
                $lead['has_previous_purchase'] = null;
            }
            $lead['sale_status'] = isset($lead['sale_status']) ? (int)$lead['sale_status'] : 0;
            if ($this->commissionService) {
                try {
                    $preview = $this->commissionService->buildLeadCommissionPreview((array)$lead);
                    $lead = array_merge($lead, $preview);
                } catch (\Throwable $e) {
                    Logger::logWarning('Failed to enrich lead commission preview', [
                        'didar_contact_id' => $lead['didar_contact_id'] ?? '',
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        }

        return $leads;
    }

    /**
     * Create new lead
     */
    public function createLead($data, bool $allowPreviousOwner = false)
    {
        // Validate input
        $fullName = Sanitizer::sanitize($data['full_name'] ?? '');
        $mobile = Sanitizer::sanitize($data['mobile_phone'] ?? '');
        $secondaryMobile = Sanitizer::sanitize($data['secondary_mobile_phone'] ?? '');
        $mobile3 = Sanitizer::sanitize($data['mobile_phone_3'] ?? '');
        $mobile4 = Sanitizer::sanitize($data['mobile_phone_4'] ?? '');
        $city = Sanitizer::sanitize($data['city'] ?? '');
        $jobTitle = Sanitizer::sanitize($data['job_title'] ?? '');
        $acquaintanceSource = Sanitizer::sanitize($data['acquaintance_source'] ?? '');
        $acquaintanceDetail = Sanitizer::sanitize($data['acquaintance_detail'] ?? '');
        $contentTopic = Sanitizer::sanitize($data['content_topic'] ?? '');
        $createdVia = Sanitizer::sanitize($data['created_via'] ?? 'manual') ?: 'manual';
        $createdByDidarId = Sanitizer::sanitize($data['created_by_didar_id'] ?? ($_SESSION['didar_id'] ?? ''));
        $createdByName = Sanitizer::sanitize($data['created_by_name'] ?? (($_SESSION['display_name'] ?? '') ?: ($_SESSION['name'] ?? '')));
        $nationalId = Sanitizer::sanitize($data['national_id'] ?? '');
        $backgroundInfo = Sanitizer::sanitize($data['background_info'] ?? '');
        $ownerId = Sanitizer::sanitize($data['owner_didar_id'] ?? '');
        $previousOwnerId = null;
        if ($allowPreviousOwner) {
            $prevSanitized = Sanitizer::sanitize($data['previous_owner_id'] ?? '');
            $previousOwnerId = $prevSanitized !== '' ? $prevSanitized : null;
        }
        
        // Validate fields
        if (!empty($fullName)) {
            $nameValidation = Validator::validatePersianText($fullName, 'نام و نام خانوادگی');
            if (!$nameValidation['valid']) {
                throw new \Exception($nameValidation['message']);
            }
        }
        
        if (!empty($nationalId)) {
            $nationalIdValidation = Validator::validateNationalId($nationalId);
            if (!$nationalIdValidation['valid']) {
                throw new \Exception($nationalIdValidation['message']);
            }
            $nationalId = $nationalIdValidation['cleaned'];
        }
        
        if (!empty($mobile)) {
            $mobileValidation = Validator::validatePhone($mobile, 'شماره تماس اصلی');
            if (!$mobileValidation['valid']) {
                throw new \Exception($mobileValidation['message']);
            }
            $mobile = $mobileValidation['cleaned'];
        }
        
        if (!empty($secondaryMobile)) {
            $secondaryValidation = Validator::validatePhone($secondaryMobile, 'شماره تماس دوم');
            if (!$secondaryValidation['valid']) {
                throw new \Exception($secondaryValidation['message']);
            }
            $secondaryMobile = $secondaryValidation['cleaned'];
        }

        if (!empty($mobile3)) {
            $mobile3Validation = Validator::validatePhone($mobile3, 'شماره تماس سوم');
            if (!$mobile3Validation['valid']) {
                throw new \Exception($mobile3Validation['message']);
            }
            $mobile3 = $mobile3Validation['cleaned'];
        }

        if (!empty($mobile4)) {
            $mobile4Validation = Validator::validatePhone($mobile4, 'شماره تماس چهارم');
            if (!$mobile4Validation['valid']) {
                throw new \Exception($mobile4Validation['message']);
            }
            $mobile4 = $mobile4Validation['cleaned'];
        }
        
        if (empty($mobile) || empty($ownerId)) {
            throw new \Exception("موبایل و مسئول لید الزامی است");
        }
        
        // Verify ownerId exists in users table (like old code)
        $db = $this->personRepo->getDb();
        $pdo = $db->getPdo();
        $hasDisplayName = $db->hasColumn('users', 'display_name');
        
        if ($hasDisplayName) {
            $ownerCheck = $pdo->prepare("SELECT didar_user_id, display_name FROM users WHERE didar_user_id = ? LIMIT 1");
        } else {
            $ownerCheck = $pdo->prepare("SELECT didar_user_id FROM users WHERE didar_user_id = ? LIMIT 1");
        }
        $ownerCheck->execute([$ownerId]);
        $ownerUser = $ownerCheck->fetch(\PDO::FETCH_ASSOC);
        
        if (!$ownerUser) {
            Logger::logError("Owner ID not found in users table", null, ['owner_id' => $ownerId]);
            throw new \Exception("مسئول انتخاب شده در سیستم یافت نشد. لطفاً دوباره انتخاب کنید.");
        }
        
        // برای OwnerId در Didar API، باید از didar_user_id استفاده کنیم
        // که می‌تواند Id یا UserId باشد - هر دو باید کار کنند
        $verifiedOwnerId = $ownerUser['didar_user_id'];
        
        // اگر owner_id در request وجود دارد (از JavaScript ارسال شده)، از آن استفاده کن
        // در غیر این صورت از didar_user_id استفاده کن
        if (!empty($data['owner_id'])) {
            $verifiedOwnerId = Sanitizer::sanitize($data['owner_id']);
            Logger::logInfo("Using owner_id from request", ['owner_id' => $verifiedOwnerId]);
        }
        
        if ($this->isDidarWriteEnabled()) {
            Logger::logInfo("Creating lead in Didar", [
                'owner_id' => $verifiedOwnerId,
                'owner_name' => $ownerUser['display_name'] ?? '',
                'owner_found_in_db' => true
            ]);
        }

        // Collect phones for duplicate detection (normalized variants)
        $phonesToCheck = [];
        foreach ([$mobile, $secondaryMobile, $mobile3, $mobile4] as $p) {
            $phonesToCheck = array_merge($phonesToCheck, PhoneNormalizer::normalizeCandidates($p));
        }
        $phonesToCheck = array_values(array_unique(array_filter($phonesToCheck)));

        // Pre-flight duplicate detection: first local, then Didar search if not found locally
        $dupMatch = $this->resolveDuplicateMatchLocalOnly($phonesToCheck);
        
        Logger::logInfo("Pre-flight duplicate check", [
            'has_person' => !empty($dupMatch['person']),
            'matches_count' => count($dupMatch['matches'] ?? []),
            'phones_checked' => $phonesToCheck
        ]);
        
        // اگر در دیتابیس محلی پیدا نشد، دیگر به دیدار سرچ نمی‌کنیم (صرفاً محلی)
        
        $hasPerson = !empty($dupMatch['person']);
        $matchCount = count($dupMatch['matches'] ?? []);

        // اگر شماره تلفن در سیستم وجود دارد، حتماً duplicate تلقی کن و اجازه ایجاد contact جدید نده
        if ($hasPerson || $matchCount > 0) {
            Logger::logInfo("Duplicate phone number found - BLOCKING contact creation", [
                'has_person' => $hasPerson,
                'matches_count' => $matchCount,
                'phones_checked' => $phonesToCheck,
                'action' => 'BLOCKING_NEW_CONTACT_CREATION'
            ]);

            $duplicatePerson = $dupMatch['person'];
            $matchedPhone = $dupMatch['matched_phone'];

            // یکتاسازی بر اساس contact_id - فقط اولین match با contact_id را نگه دار
            $allMatches = $dupMatch['matches'] ?? [];
            $matchesList = $this->dedupeMatchesByContactId($allMatches);

            // پیدا کردن اولین contact معتبر
            $primaryContact = null;
            $primaryMatch = null;
            foreach ($matchesList as $match) {
                if (!empty($match['contact_id'])) {
                    $contact = $this->personRepo->findByDidarId($match['contact_id']);
                    if ($contact) {
                        $primaryContact = $contact;
                        $primaryMatch = $match;
                        break;
                    }
                }
            }

            // اگر contact اصلی پیدا نشد، از person موجود استفاده کن
            if (!$primaryContact && $duplicatePerson) {
                $primaryContact = $duplicatePerson;
                $primaryMatch = $matchesList[0] ?? null;
            }

            $ownerId = $primaryContact ? ($primaryContact->owner_didar_id ?? '') : '';
            $ownerName = $this->getUserNameByDidarId($ownerId);

            Logger::logWarning("Contact creation blocked due to duplicate phone", [
                'blocked_phone' => $matchedPhone,
                'existing_contact_id' => $primaryContact ? $primaryContact->didar_contact_id : 'unknown',
                'existing_owner_id' => $ownerId,
                'existing_owner_name' => $ownerName,
                'all_matched_phones' => array_column($matchesList, 'phone'),
                'user_attempting_creation' => $data['owner_didar_id'] ?? 'unknown',
                'reason' => 'PREVENTING_DATA_FRAGMENTATION',
                'phones_checked' => $phonesToCheck
            ]);

            return [
                'is_duplicate' => true,
                'person' => $primaryContact,
                'matched_phone' => $matchedPhone,
                'matches' => [$primaryMatch],
                'owner_id' => $ownerId,
                'owner_name' => $ownerName,
                'contact_id' => $primaryContact ? $primaryContact->id : null, // Return local ID for UI consistency
                'didar_contact_id' => $primaryContact ? $primaryContact->didar_contact_id : null,
                'block_reason' => 'DUPLICATE_PHONE_PREVENTION'
            ];
        } else {
            // اگر شماره تلفن در سیستم وجود ندارد، اجازه ایجاد contact جدید بده
            Logger::logInfo("Phone number not found in system - allowing new contact creation", [
                'phones_checked' => $phonesToCheck,
                'action' => 'ALLOWING_NEW_CONTACT_CREATION'
            ]);
        }
        
        // Split full name
        $nameParts = explode(' ', trim($fullName), 2);
        $firstName = $nameParts[0] ?? '';
        $lastName = $nameParts[1] ?? '';
        
        if (empty($lastName)) {
            if (!empty($firstName)) {
                $lastName = $firstName;
            } else {
                $firstName = 'لید';
                $lastName = 'جدید';
            }
        }
        
        $didarWriteEnabled = $this->isDidarWriteEnabled();
        if (!$didarWriteEnabled) {
            Logger::logInfo("Didar write disabled; creating local lead only", [
                'owner_id' => $verifiedOwnerId,
                'mobile' => $mobile,
                'full_name' => trim($fullName ?: ($firstName . ' ' . $lastName))
            ]);

            $contactId = uniqid('local_', true);
            $person = new \App\Models\Person([
                'didar_contact_id' => $contactId,
                'owner_didar_id' => $verifiedOwnerId,
                'previous_owner_id' => $previousOwnerId,
                'code' => '',
                'first_name' => $firstName,
                'last_name' => $lastName,
                'mobile_phone' => $mobile,
                'secondary_mobile_phone' => $secondaryMobile,
                'mobile_phone_3' => $mobile3,
                'mobile_phone_4' => $mobile4,
                'work_phone' => '',
                'email' => '',
                'contact_type' => 'Lead',
                'city' => $city,
                'job_title' => $jobTitle,
                'acquaintance_source' => $acquaintanceSource,
                'acquaintance_detail' => $acquaintanceDetail,
                'content_topic' => $contentTopic,
                'created_by_didar_id' => $createdByDidarId,
                'created_by_name' => $createdByName,
                'created_via' => $createdVia,
                'national_id' => $nationalId,
                'has_previous_purchase' => null,
                'sale_status' => 0,
                'register_time_jalali' => $data['register_time_jalali'] ?? null,
                'register_time' => $data['register_time'] ?? date('Y-m-d H:i:s'),
                'visibility_type' => '',
                'background_info' => $backgroundInfo,
                'customer_level' => Sanitizer::sanitize($data['customer_level'] ?? '') ?: null,
                'customer_level_notes' => Sanitizer::sanitize($data['customer_level_notes'] ?? '') ?: null,
                'last_sync' => date('Y-m-d H:i:s'),
                'raw_json' => json_encode(['local_only' => true, 'created_at' => date('Y-m-d H:i:s')], JSON_UNESCAPED_UNICODE)
            ]);

            $localId = $this->personRepo->save($person);
            $person->id = $localId;

            $deal = $this->createLocalDealForContact($contactId, $verifiedOwnerId, '', [
                'acquaintance_source' => $acquaintanceSource,
                'acquaintance_detail' => $acquaintanceDetail,
                'content_topic' => $contentTopic,
                'created_by_didar_id' => $createdByDidarId,
                'created_by_name' => $createdByName,
                'created_via' => $createdVia
            ]);

            if ($this->notificationService) {
                $leadName = trim($firstName . ' ' . $lastName);
                $this->notificationService->notifyLeadAssigned($ownerId, $contactId, $leadName);
            }

            return [
                'is_duplicate' => false,
                'contact_id' => $person->id,
                'didar_contact_id' => $contactId,
                'deal_id' => $deal ? $deal->didar_deal_id : null,
                'message' => "لید با موفقیت ایجاد شد"
            ];
        }

        // Create in Didar
        $contactData = [
            'Contact' => [
                'Type' => 'Person',
                'FirstName' => $firstName,
                'LastName' => $lastName,
                'DisplayName' => trim($fullName ?: ($firstName . ' ' . $lastName)),
                'OwnerId' => $verifiedOwnerId, // Verified to exist in users table
                'MobilePhone' => $mobile,
                'City' => $city,
                'JobTitle' => $jobTitle,
                'BackgroundInfo' => trim($backgroundInfo . ($acquaintanceSource ? "\nشیوه آشنایی: $acquaintanceSource ($acquaintanceDetail)" : '') . ($contentTopic ? "\nموضوع محتوا: $contentTopic" : ''))
            ]
        ];
        
        // Phone fields beyond primary are stored locally only to avoid Didar duplicate rejections
        // Additional phones stored locally only
        if (!empty($nationalId)) {
            $contactData['Contact']['NationalId'] = $nationalId;
        }
        
        // Create in Didar ONLY if not duplicate locally
        Logger::logInfo("About to call contact/save", [
            'mobile' => $mobile,
            'phones_checked' => $phonesToCheck,
            'has_duplicate_check' => true
        ]);
        $res = $this->didarApi->call('/contact/save', 'POST', $contactData);
        
        if (isset($res['error']) || !isset($res['Response']['Id'])) {
            $errorMsg = $res['error'] ?? ($res['message'] ?? 'Unknown error');
            // اگر دیدار duplicate برگرداند، پاسخ تکراری بده و استثناء نده
            if (stripos($errorMsg, 'duplicate') !== false) {
                // اول محلی چک کن
                $dupMatch = $this->resolveDuplicateMatchLocalOnly($phonesToCheck);
                
                // اگر در محلی پیدا نشد، در دیدار سرچ کن
                if (!$dupMatch['person'] || empty($dupMatch['matches'])) {
                    $didarDupMatch = $this->resolveDuplicateMatch($phonesToCheck, true);
                    if ($didarDupMatch['person'] || !empty($didarDupMatch['matches'])) {
                        $dupMatch = $didarDupMatch;
                    } else {
                        // اگر هنوز پیدا نشد، از شماره اصلی در دیدار سرچ کن
                        $searchRes = $this->didarApi->searchContactsByPhonesDetailed([$mobile], 'MobilePhone');
                        $didarContacts = $searchRes['contacts'] ?? [];
                        if (empty($didarContacts)) {
                            $searchRes = $this->didarApi->searchContactsByPhonesDetailed([$mobile], 'Phone');
                            $didarContacts = $searchRes['contacts'] ?? [];
                        }
                        
                        if (!empty($didarContacts)) {
                            $c = $didarContacts[0];
                            $contactId = $c['Id'] ?? ($c['ContactId'] ?? null);
                            $ownerId = $c['OwnerId'] ?? ($c['Owner']['Id'] ?? ($c['Owner']['OwnerId'] ?? ''));
                            $displayName = $c['DisplayName'] ?? ($c['FullName'] ?? (($c['FirstName'] ?? '') . ' ' . ($c['LastName'] ?? '')));
                            $ownerNameFallback = $c['Owner']['Name'] ?? ($c['Owner']['DisplayName'] ?? '');
                            $ownerName = $this->getUserNameByDidarId($ownerId);
                            if (empty($ownerName) && !empty($ownerNameFallback)) {
                                $ownerName = $ownerNameFallback;
                            }
                            
                            $dupMatch['matches'] = [[
                                'phone' => $mobile,
                                'contact_id' => $contactId,
                                'owner_id' => $ownerId,
                                'owner_name' => $ownerName,
                                'contact_name' => trim($displayName),
                                'found' => !empty($contactId)
                            ]];
                            $dupMatch['matched_phone'] = $mobile;
                        }
                    }
                }
                
                $dupPerson = $dupMatch['person'] ?? null;
                $matched = $dupMatch['matched_phone'] ?? $mobile;
                $matchCount = count($dupMatch['matches'] ?? []);
                // یکتاسازی بر اساس contact_id - فقط اولین match با contact_id را نگه دار
                $allMatches = $dupMatch['matches'] ?? [];
                $matchesList = $this->dedupeMatchesByContactId($allMatches);
                
                // فقط اولین match با contact_id را نگه دار (برای نمایش)
                $firstMatchWithContact = null;
                foreach ($matchesList as $match) {
                    if (!empty($match['contact_id'])) {
                        $firstMatchWithContact = $match;
                        break;
                    }
                }
                // اگر match با contact_id پیدا شد، فقط همان را نمایش بده
                if ($firstMatchWithContact) {
                    $matchesList = [$firstMatchWithContact];
                } else if (!empty($matchesList)) {
                    // اگر match با contact_id پیدا نشد، فقط اولین match را نمایش بده
                    $matchesList = [$matchesList[0]];
                }
                
                // اطمینان از اینکه owner_name از matches یا دیتابیس محلی گرفته می‌شود
                $ownerId = '';
                $ownerName = '';
                
                // اول از matches بگیر (اولویت با matches است چون از دیدار می‌آید)
                if (!empty($matchesList)) {
                    $firstMatch = $matchesList[0];
                    if (!empty($firstMatch['owner_id'])) {
                        $ownerId = $firstMatch['owner_id'];
                    }
                    if (!empty($firstMatch['owner_name'])) {
                        $ownerName = $firstMatch['owner_name'];
                    }
                }
                
                // اگر از person موجود است، از آن استفاده کن (اولویت با دیتابیس محلی)
                if ($dupPerson && !empty($dupPerson->owner_didar_id)) {
                    $ownerId = $dupPerson->owner_didar_id;
                    $ownerName = $this->getUserNameByDidarId($ownerId);
                }
                
                // اگر owner_id پیدا شد اما owner_name خالی است، از دیتابیس محلی بگیر
                if (!empty($ownerId) && empty($ownerName)) {
                    $ownerName = $this->getUserNameByDidarId($ownerId);
                }
                
                // اگر هنوز owner_name خالی است، لاگ کن
                if (empty($ownerName)) {
                    Logger::logError("Owner name not found for duplicate lead from Didar", null, [
                        'owner_id' => $ownerId,
                        'contact_id' => $contactId,
                        'matches_count' => count($matchesList),
                        'has_person' => !empty($dupPerson),
                        'phones_checked' => $phonesToCheck
                    ]);
                }
                
                // contact_name را از person یا matches بگیر
                $contactName = trim($fullName ?: ($firstName . ' ' . $lastName));
                if ($dupPerson) {
                    $contactName = trim(($dupPerson->first_name ?? '') . ' ' . ($dupPerson->last_name ?? ''));
                } else if (!empty($matchesList)) {
                    $firstMatch = $matchesList[0];
                    $contactName = $firstMatch['contact_name'] ?? $contactName;
                }

                // پیدا کردن contact_id از matches اگر person null است
                $contactId = null;
                if ($dupPerson) {
                    $contactId = $dupPerson->didar_contact_id;
                } else if (!empty($matchesList)) {
                    $firstMatch = $matchesList[0];
                    $contactId = $firstMatch['contact_id'] ?? null;
                }

                // اگر شخص محلی نداریم و بیش از 1 match داریم، duplicate برنگردان و ادامه ایجاد لید
                if (!$dupPerson && $matchCount !== 1) {
                    Logger::logInfo("Didar duplicate ignored (no local person, matches != 1)", [
                        'matches_count' => $matchCount,
                        'phones_checked' => $phonesToCheck
                    ]);
                    // از بلوک duplicate خارج می‌شویم تا در ادامه لید محلی ساخته شود
                } else {
                    // اگر در محلی شخص نداریم ولی contact_id داریم و فقط یک match داریم، یک رکورد حداقلی محلی بساز
                    if (!$dupPerson && $contactId && $matchCount === 1) {
                        $dupPerson = $this->ensureLocalPersonFromMatch($contactId, $ownerId, $contactName, $matched);
                    }

                    $deals = $contactId ? $this->dealRepo->findByContactId($contactId) : [];
                    $dealIds = array_map(fn($d) => $d->didar_deal_id, $deals);

                    return [
                        'is_duplicate' => true,
                        'needs_confirmation' => false,
                        'contact_id' => $contactId,
                        'contact_name' => $contactName,
                        'owner_id' => $ownerId,
                        'owner_name' => $ownerName,
                        'matched_phone' => $matched,
                        'deal_ids' => $dealIds,
                        'matches' => $matchesList,
                        'message' => 'این شماره در دیدار تکراری است'
                    ];
                }
            }
            throw new \Exception("خطا در ایجاد لید در دیدار: " . $errorMsg);
        }
        
        $contactId = $res['Response']['Id'];
        
        // Save locally
        $person = new \App\Models\Person([
            'didar_contact_id' => $contactId,
            'owner_didar_id' => $verifiedOwnerId,
            'previous_owner_id' => $previousOwnerId,
            'code' => $res['Response']['Code'] ?? '',
            'first_name' => $firstName,
            'last_name' => $lastName,
            'mobile_phone' => $mobile,
            'secondary_mobile_phone' => $secondaryMobile,
            'mobile_phone_3' => $mobile3,
            'mobile_phone_4' => $mobile4,
            'work_phone' => '',
            'email' => '',
            'contact_type' => 'Lead',
            'city' => $city,
            'job_title' => $jobTitle,
            'acquaintance_source' => $acquaintanceSource,
            'acquaintance_detail' => $acquaintanceDetail,
            'content_topic' => $contentTopic,
            'created_by_didar_id' => $createdByDidarId,
            'created_by_name' => $createdByName,
            'created_via' => $createdVia,
            'national_id' => $nationalId,
            // Unknown by default; will be set after the first manual answer
            'has_previous_purchase' => null,
            'sale_status' => 0,
            'register_time_jalali' => $data['register_time_jalali'] ?? null,
            'register_time' => $data['register_time'] ?? date('Y-m-d H:i:s'),
            'visibility_type' => '',
            'background_info' => $backgroundInfo,
            'customer_level' => Sanitizer::sanitize($data['customer_level'] ?? '') ?: null,
            'customer_level_notes' => Sanitizer::sanitize($data['customer_level_notes'] ?? '') ?: null,
            'last_sync' => date('Y-m-d H:i:s'),
            'raw_json' => json_encode($res['Response'], JSON_UNESCAPED_UNICODE)
        ]);
        
        $localId = $this->personRepo->save($person);
        $person->id = $localId;

        // Create initial local deal in pipeline "معامله جدید"
        $deal = $this->createLocalDealForContact($contactId, $verifiedOwnerId, "معامله جدید", [
            'acquaintance_source' => $acquaintanceSource,
            'acquaintance_detail' => $acquaintanceDetail,
            'content_topic' => $contentTopic,
            'created_by_didar_id' => $createdByDidarId,
            'created_by_name' => $createdByName,
            'created_via' => $createdVia
        ]);
        
        // Notify owner about new lead assignment
        if ($this->notificationService) {
            $leadName = trim($firstName . ' ' . $lastName);
            $this->notificationService->notifyLeadAssigned($ownerId, $contactId, $leadName);
        }

        // AUDIT TRAIL: Log contact creation for data integrity tracking
        Logger::logInfo('AUDIT: Contact created successfully', [
            'action' => 'CREATE_CONTACT',
            'contact_id' => $contactId,
            'contact_name' => trim($firstName . ' ' . $lastName),
            'mobile_phone' => $mobile,
            'secondary_mobile_phone' => $secondaryMobile,
            'owner_id' => $verifiedOwnerId,
            'previous_owner_id' => $previousOwnerId,
            'user_id' => $_SESSION['didar_id'] ?? 'system',
            'user_name' => $_SESSION['name'] ?? 'system',
            'server_time' => date('Y-m-d H:i:s'),
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'phones_created' => array_filter([$mobile, $secondaryMobile, $mobile3, $mobile4])
        ]);

        return [
            'is_duplicate' => false,
            'contact_id' => $person->id,
            'didar_contact_id' => $contactId,
            'deal_id' => $deal ? $deal->didar_deal_id : null,
            'message' => "لید با موفقیت ایجاد شد"
        ];
    }

    /**
     * Create a local deal for a contact with default pipeline stage
     */
    private function createLocalDealForContact(string $contactId, string $ownerId, string $title = '', array $extra = []): ?Deal
    {
        try {
            $defaultTitle = "\u{0645}\u{0639}\u{0627}\u{0645}\u{0644}\u{0647} \u{062c}\u{062f}\u{06cc}\u{062f}";
            if ($title === '' || $title === $defaultTitle) {
                $person = $this->personRepo->findByDidarId($contactId);
                if ($person && !empty($person->mobile_phone)) {
                    $existingDeals = $this->dealRepo->countDealsByContactId($contactId);
                    $ordinals = [
                        0 => "\u{0627}\u{0648}\u{0644}",
                        1 => "\u{062f}\u{0648}\u{0645}",
                        2 => "\u{0633}\u{0648}\u{0645}",
                        3 => "\u{0686}\u{0647}\u{0627}\u{0631}\u{0645}",
                        4 => "\u{067e}\u{0646}\u{062c}\u{0645}",
                        5 => "\u{0634}\u{0634}\u{0645}",
                        6 => "\u{0647}\u{0641}\u{062a}\u{0645}",
                        7 => "\u{0647}\u{0634}\u{062a}\u{0645}",
                        8 => "\u{0646}\u{0647}\u{0645}",
                        9 => "\u{062f}\u{0647}\u{0645}"
                    ];
                    $ordinal = isset($ordinals[$existingDeals])
                        ? $ordinals[$existingDeals]
                        : ($existingDeals + 1) . "\u{0627}\u{0645}";
                    $title = "\u{0645}\u{0639}\u{0627}\u{0645}\u{0644}\u{0647} {$ordinal} {$person->mobile_phone}";
                } else {
                    Logger::logWarning('createLocalDealForContact: missing person or mobile', [
                        'contact_id' => $contactId
                    ]);
                }
            }

            $finalTitle = $title !== '' ? $title : $defaultTitle;

            $deal = new Deal([
                'didar_deal_id' => uniqid('local_'),
                'owner_didar_id' => $ownerId,
                'contact_didar_id' => $contactId,
                'title' => $finalTitle,
                'status' => 'Pending',
                'pipeline_stage' => $defaultTitle,
                'acquaintance_source' => Sanitizer::sanitize($extra['acquaintance_source'] ?? ''),
                'acquaintance_detail' => Sanitizer::sanitize($extra['acquaintance_detail'] ?? ''),
                'content_topic' => Sanitizer::sanitize($extra['content_topic'] ?? ''),
                'created_by_didar_id' => Sanitizer::sanitize($extra['created_by_didar_id'] ?? ($_SESSION['didar_id'] ?? '')),
                'created_by_name' => Sanitizer::sanitize($extra['created_by_name'] ?? (($_SESSION['display_name'] ?? '') ?: ($_SESSION['name'] ?? $this->getUserNameByDidarId($ownerId)))),
                'created_via' => Sanitizer::sanitize($extra['created_via'] ?? 'manual') ?: 'manual',
                'register_time' => date('Y-m-d H:i:s'),
                'last_sync' => date('Y-m-d H:i:s'),
                'payable_amount' => 0,  // Default to 0 so deal can be counted in KPI calculations
                'payment_amount' => 0   // Default to 0 so deal can be counted in KPI calculations
            ]);
            $this->dealRepo->save($deal);

            // Assign a customer code to the lead when a deal is created (idempotent: only once).
            $this->personRepo->assignCustomerCodeIfMissing($contactId);

            return $deal;
        } catch (\Exception $e) {
            Logger::logError('Failed to create local deal for contact', $e, [
                'contact_id' => $contactId
            ]);
            return null;
        }
    }

    public function createAdditionalDealForContact(string $contactId, string $ownerId, string $title = '', array $extra = []): ?Deal
    {
        $safeContactId = trim($contactId);
        $safeOwnerId = trim($ownerId);
        if ($safeContactId === '' || $safeOwnerId === '') {
            return null;
        }
        return $this->createLocalDealForContact($safeContactId, $safeOwnerId, $title, $extra);
    }
/**
     * Check for duplicate phones locally.
     * Throws exception if duplicate found.
     */
    private function checkForDuplicates(array $phones, ?string $excludeDidarId = null, ?int $excludeLocalId = null): void
    {
        $phonesToCheck = [];
        foreach ($phones as $p) {
            $phonesToCheck = array_merge($phonesToCheck, PhoneNormalizer::normalizeCandidates($p));
        }
        $phonesToCheck = array_values(array_unique(array_filter($phonesToCheck)));

        if (empty($phonesToCheck)) {
            return;
        }

        $dupMatch = $this->resolveDuplicateMatchLocalOnly($phonesToCheck);
        
        if (!empty($dupMatch['person'])) {
            $duplicatePerson = $dupMatch['person'];
            
            // CRITICAL: Check BOTH local ID and Didar ID to prevent collisions between numeric IDs
            $isSamePerson = false;
            if ($excludeDidarId && $duplicatePerson->didar_contact_id === $excludeDidarId) {
                $isSamePerson = true;
            }
            if ($excludeLocalId && (int)$duplicatePerson->id === (int)$excludeLocalId) {
                $isSamePerson = true;
            }

            if ($isSamePerson) {
                Logger::logDebug("checkForDuplicates: Found self - ignoring duplicate", [
                    'phone' => $dupMatch['matched_phone'] ?? 'unknown',
                    'id' => $duplicatePerson->id,
                    'didar_id' => $duplicatePerson->didar_contact_id
                ]);
                return;
            }

            Logger::logInfo("checkForDuplicates: REAL duplicate found", [
                'phone' => $dupMatch['matched_phone'] ?? 'unknown',
                'found_id' => $duplicatePerson->id,
                'found_didar_id' => $duplicatePerson->didar_contact_id,
                'exclude_id' => $excludeLocalId,
                'exclude_didar_id' => $excludeDidarId
            ]);

            $ownerName = $this->getUserNameByDidarId($duplicatePerson->owner_didar_id);
            $msg = "این شماره قبلاً برای {$duplicatePerson->first_name} {$duplicatePerson->last_name} (مسئول: $ownerName) ثبت شده است.";
            throw new \Exception($msg);
        }
    }

    /**
     * Update lead
     */
    public function updateLead($contactId, $data, bool $allowPreviousOwner = false, bool $allowCustomerCodeEdit = false)
    {
        // Controller passes standardized didar_contact_id here.
        // Use strict Didar lookup to avoid numeric id collisions.
        $person = $this->personRepo->findByDidarId($contactId);
        if (!$person) {
            // Defensive fallback only for edge cases.
            $person = $this->personRepo->findById($contactId);
        }
        
        if (!$person) {
            throw new \Exception("لید یافت نشد");
        }

        // Standardize variables
        $contactId = $person->didar_contact_id;
        $localId = $person->id;

        // ... rest of the logic ...

        $ownerChange = null;
        if ($allowPreviousOwner && isset($data['owner_didar_id'])) {
            $newOwnerId = Sanitizer::sanitize($data['owner_didar_id'] ?? '');
            if ($newOwnerId !== '' && $newOwnerId !== ($person->owner_didar_id ?? '')) {
                $oldOwnerId = $person->owner_didar_id ?? '';
                $this->transferLeadOwner($contactId, $newOwnerId);
                // Keep in-memory model in sync (prevents later save from reverting)
                $person->previous_owner_id = $oldOwnerId ?: null;
                $person->owner_didar_id = $newOwnerId;
                $ownerChange = [
                    'old_owner_id' => $oldOwnerId ?: null,
                    'new_owner_id' => $newOwnerId,
                ];
            }
        }
        
        // Collect phones for duplicate check
        $phonesToValidate = [];
        
        // Helper to normalize any phone for comparison
        $normalize = function($p) {
            $p = preg_replace('/\D/', '', $p ?? '');
            if (strlen($p) === 11 && substr($p, 0, 1) === '0') return substr($p, 1);
            return $p;
        };

        // Get all current phones of this person to avoid checking them as duplicates
        $currentPhones = array_filter(array_map($normalize, [
            $person->mobile_phone,
            $person->secondary_mobile_phone,
            $person->mobile_phone_3,
            $person->mobile_phone_4
        ]));

        // Update fields
        if (isset($data['first_name'])) {
            $person->first_name = Sanitizer::sanitize($data['first_name']);
        }
        if (isset($data['last_name'])) {
            $person->last_name = Sanitizer::sanitize($data['last_name']);
        }
        
        // --- Primary Phone ---
        if (isset($data['mobile_phone'])) {
            $newPrimaryRaw = Sanitizer::sanitize($data['mobile_phone']);
            $validation = Validator::validatePhone($newPrimaryRaw, 'شماره تماس اصلی');
            if (!$validation['valid']) {
                throw new \Exception($validation['message']);
            }
            
            $cleaned = $validation['cleaned'];
            $existingNormalized = $normalize($person->mobile_phone);

            if ($existingNormalized !== $cleaned) {
                // If the phone is NOT already one of this person's phones, we must check it for duplicates
                if (!in_array($cleaned, $currentPhones)) {
                    // If not admin, block primary phone changes
                    if (!$allowPreviousOwner) { 
                        throw new \Exception('شماره تماس اصلی پس از ثبت قابل تغییر نیست.');
                    }
                    $phonesToValidate[] = $cleaned;
                }

                // Log and update
                if ($this->phoneChangeLogService) {
                    $this->phoneChangeLogService->logPhoneChange(
                        $contactId,
                        trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                        'mobile_phone',
                        $person->mobile_phone,
                        $cleaned,
                        'update'
                    );
                }
                $person->mobile_phone = $cleaned;
            }
        }

        // --- Secondary Phone ---
        if (isset($data['secondary_mobile_phone'])) {
            $newSecondaryRaw = Sanitizer::sanitize($data['secondary_mobile_phone']);
            $validation = Validator::validatePhone($newSecondaryRaw, 'شماره تماس دوم');
            if (!$validation['valid']) {
                throw new \Exception($validation['message']);
            }

            $cleaned = $validation['cleaned'];
            $existingNormalized = $normalize($person->secondary_mobile_phone);

            if ($existingNormalized !== $cleaned) {
                 if (!empty($cleaned) && !in_array($cleaned, $currentPhones)) {
                     $phonesToValidate[] = $cleaned;
                 }
                 
                 if ($this->phoneChangeLogService) {
                     $action = empty($person->secondary_mobile_phone) ? 'add' : (empty($cleaned) ? 'delete' : 'update');
                     $this->phoneChangeLogService->logPhoneChange(
                         $contactId,
                         trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                         'secondary_mobile_phone',
                         $person->secondary_mobile_phone,
                         $cleaned,
                         $action
                     );
                 }
                 $person->secondary_mobile_phone = $cleaned;
            }
        }

        // --- Mobile Phone 3 ---
        if (isset($data['mobile_phone_3'])) {
            $newMobile3 = Sanitizer::sanitize($data['mobile_phone_3']);
            $validation = Validator::validatePhone($newMobile3, 'شماره تماس سوم');
            if (!$validation['valid']) {
                throw new \Exception($validation['message']);
            }

            $cleaned = $validation['cleaned'];
            $existingNormalized = $normalize($person->mobile_phone_3);

            if ($existingNormalized !== $cleaned) {
                 if (!empty($cleaned) && !in_array($cleaned, $currentPhones)) {
                     $phonesToValidate[] = $cleaned;
                 }
                 
                 if ($this->phoneChangeLogService) {
                     $action = empty($person->mobile_phone_3) ? 'add' : (empty($cleaned) ? 'delete' : 'update');
                     $this->phoneChangeLogService->logPhoneChange(
                         $contactId,
                         trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                         'mobile_phone_3',
                         $person->mobile_phone_3,
                         $cleaned,
                         $action
                     );
                 }
                 $person->mobile_phone_3 = $cleaned;
            }
        }

        // --- Mobile Phone 4 ---
        if (isset($data['mobile_phone_4'])) {
            $newMobile4 = Sanitizer::sanitize($data['mobile_phone_4']);
            $validation = Validator::validatePhone($newMobile4, 'شماره تماس چهارم');
            if (!$validation['valid']) {
                throw new \Exception($validation['message']);
            }

            $cleaned = $validation['cleaned'];
            $existingNormalized = $normalize($person->mobile_phone_4);

            if ($existingNormalized !== $cleaned) {
                 if (!empty($cleaned) && !in_array($cleaned, $currentPhones)) {
                     $phonesToValidate[] = $cleaned;
                 }
                 
                 if ($this->phoneChangeLogService) {
                     $action = empty($person->mobile_phone_4) ? 'add' : (empty($cleaned) ? 'delete' : 'update');
                     $this->phoneChangeLogService->logPhoneChange(
                         $contactId,
                         trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                         'mobile_phone_4',
                         $person->mobile_phone_4,
                         $cleaned,
                         $action
                     );
                 }
                 $person->mobile_phone_4 = $cleaned;
            }
        }
        
        // Validate duplicates for new/changed phones
        if (!empty($phonesToValidate)) {
            $this->checkForDuplicates($phonesToValidate, $contactId, $localId);
        }

        if (isset($data['email'])) {
            $person->email = Sanitizer::sanitize($data['email']);
        }
        if (isset($data['city'])) {
            $person->city = Sanitizer::sanitize($data['city']);
        }
        if (isset($data['job_title'])) {
            $person->job_title = Sanitizer::sanitize($data['job_title']);
        }
        if (isset($data['background_info'])) {
            $person->background_info = Sanitizer::sanitize($data['background_info']);
        }
        if ($allowPreviousOwner && isset($data['previous_owner_id'])) {
            $prevOwner = Sanitizer::sanitize($data['previous_owner_id']);
            $person->previous_owner_id = $prevOwner !== '' ? $prevOwner : null;
        }
        if (isset($data['national_id'])) {
            $newNational = Sanitizer::sanitize($data['national_id']);
            $validation = Validator::validateNationalId($newNational);
            if (!$validation['valid']) {
                throw new \Exception($validation['message']);
            }
            $cleanedNational = $validation['cleaned'] ?? '';

            // Empty input means "no change" during update.
            // National ID is immutable only when a new non-empty different value is submitted.
            if ($cleanedNational !== '' && !empty($person->national_id) && $person->national_id !== $cleanedNational) {
                throw new \Exception('کد ملی پس از ثبت قابل تغییر نیست.');
            }
            if (empty($person->national_id) && $cleanedNational !== '') {
                $person->national_id = $cleanedNational;
            }
        }
        
        // Additional Fields (Issue 3)
        if (isset($data['job_description'])) {
            $person->job_description = Sanitizer::sanitize($data['job_description']);
        }
        if (isset($data['acquaintance_duration'])) {
            $person->acquaintance_duration = Sanitizer::sanitize($data['acquaintance_duration']);
        }
        if (isset($data['extra_info'])) {
            $person->extra_info = Sanitizer::sanitize($data['extra_info']);
        }
        if (isset($data['financial_level'])) {
            $person->financial_level = Sanitizer::sanitize($data['financial_level']);
        }
        if (isset($data['asset_estimation'])) {
            $person->asset_estimation = Sanitizer::sanitize($data['asset_estimation']);
        }
        if (isset($data['income_estimation'])) {
            $person->income_estimation = Sanitizer::sanitize($data['income_estimation']);
        }
        if (isset($data['requested_services'])) {
            $person->requested_services = Sanitizer::sanitize($data['requested_services']);
        }
        if (isset($data['customer_level'])) {
            $person->customer_level = Sanitizer::sanitize($data['customer_level']) ?: null;
        }
        if (isset($data['customer_level_notes'])) {
            $person->customer_level_notes = Sanitizer::sanitize($data['customer_level_notes']) ?: null;
        }
        if (isset($data['has_previous_purchase']) && $data['has_previous_purchase'] !== '') {
            $person->has_previous_purchase = (int)$data['has_previous_purchase'];
        }
        $prevPurchaseNum = '';
        if (isset($data['previous_purchase_number'])) {
             // Store it in extra_info or a dedicated field if exists? 
             // Schema didn't mention previous_purchase_number column, but logic in save_deal_info used it? 
             // Actually save_deal_info logic didn't use it, it was just in the form.
             // We'll append it to extra_info for now if there is no dedicated column, 
             // OR if we assume the migration added it. 
             // Let's check Schema... Schema update didn't include previous_purchase_number.
             // So we'll append to extra_info or background_info
             $prevPurchaseNum = Sanitizer::sanitize($data['previous_purchase_number']);
             if ($prevPurchaseNum) {
                 $person->extra_info = ($person->extra_info ? $person->extra_info . "\n" : "") . "شماره خرید قبلی: " . $prevPurchaseNum;
             }
        }

        // Check if lead should be transferred to CRM specialist
        // This happens when has_previous_purchase = 1 (number is optional)
        $shouldTransferToCrm = false;
        if (isset($data['has_previous_purchase']) && $data['has_previous_purchase'] == '1') {
            $shouldTransferToCrm = true;
            Logger::logInfo("CRM Transfer Check - Transfer required", [
                'has_previous_purchase' => $data['has_previous_purchase'],
                'previous_purchase_number' => $data['previous_purchase_number'] ?? 'not_provided',
                'contact_id' => $contactId
            ]);
        } else {
            Logger::logInfo("CRM Transfer Check - No transfer needed", [
                'has_previous_purchase' => $data['has_previous_purchase'] ?? 'not_set',
                'contact_id' => $contactId
            ]);
        }

	        // Transfer lead to CRM specialist if needed
	        if ($shouldTransferToCrm) {
	            try {
	                $userRepo = new \App\Repositories\UserRepository($this->personRepo->getDb());
	                $crmSpecialist = $userRepo->findCrmSpecialist();
	            } catch (\Exception $e) {
	                Logger::logError("Error in CRM transfer setup", $e, ['contact_id' => $contactId]);
	                throw $e;
	            }

		            $oldOwnerId = $person->owner_didar_id;
		            $crmSpecialistId = ($crmSpecialist && $crmSpecialist->didar_user_id) ? $crmSpecialist->didar_user_id : '';
		            $newOwnerId = 'ef266a9a-ff55-4893-8be3-7848badfcdce'; // Amir Sohrabi (Didar user id)

		            try {
		                // Transfer ownership away from current user to Amir; CRM specialist only sees it in referrals.
		                if ($oldOwnerId !== $newOwnerId) {
		                    $this->transferLeadOwner($contactId, $newOwnerId);
		                    // Keep the in-memory model in sync (prevents later save/contact-save from reverting the transfer)
		                    $person->previous_owner_id = $oldOwnerId;
		                    $person->owner_didar_id = $newOwnerId;
		                }

		                // Remove all deals from pipeline stages
		                $this->dealRepo->removeDealsFromPipeline($contactId);

		                // Add referral record (only if CRM specialist exists)
		                if (!empty($crmSpecialistId)) {
		                    $this->addReferralRecord($contactId, $oldOwnerId, $crmSpecialistId, $prevPurchaseNum);
		                } else {
		                    Logger::logWarning("No CRM specialist found; referral record not created", [
		                        'contact_id' => $contactId
		                    ]);
		                }

		                Logger::logInfo("Previous purchase detected - owner moved to Amir and referral created", [
		                    'contact_id' => $contactId,
		                    'old_owner_id' => $oldOwnerId,
		                    'new_owner_id' => $newOwnerId,
		                    'crm_specialist_id' => $crmSpecialistId ?: null,
		                    'previous_purchase_number' => $prevPurchaseNum
		                ]);
		            } catch (\Exception $e) {
		                Logger::logError("Failed to process previous purchase transfer/referral", $e, [
		                    'contact_id' => $contactId,
		                    'new_owner_id' => $newOwnerId
		                ]);
		                // Don't throw exception - continue with update
		            }
	        }
        if (isset($data['acquaintance_source'])) {
            $person->acquaintance_source = Sanitizer::sanitize($data['acquaintance_source']);
        }
        if (isset($data['acquaintance_detail'])) {
            $person->acquaintance_detail = Sanitizer::sanitize($data['acquaintance_detail']);
        }
        if (isset($data['content_topic'])) {
            $person->content_topic = Sanitizer::sanitize($data['content_topic']);
        }

        if (array_key_exists('customer_code', $data)) {
            if ($allowCustomerCodeEdit) {
                $incomingCustomerCode = trim(Sanitizer::sanitize((string)$data['customer_code']));
                if ($incomingCustomerCode !== '') {
                    if (!preg_match('/^\d+$/', $incomingCustomerCode)) {
                        throw new \Exception('کد مشتری باید فقط عدد باشد.');
                    }
                    if ($this->personRepo->isCustomerCodeTakenByAnother($incomingCustomerCode, (string)$person->didar_contact_id)) {
                        throw new \Exception('این کد مشتری قبلاً برای لید دیگری ثبت شده است.');
                    }
                    $person->customer_code = $incomingCustomerCode;
                }
            } else {
                // Non-admin users can never alter customer_code.
                $person->customer_code = $person->customer_code ?? '';
            }
        }

        
        // Update in Didar (disabled by configuration)
        $didarSyncWarning = null;
        if ($this->isDidarWriteEnabled()) {
            $contactData = [
                'Contact' => [
                    'Id' => $contactId,
                    'Type' => 'Person',
                    'FirstName' => $person->first_name,
                    'LastName' => $person->last_name,
                    'DisplayName' => trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                    'MobilePhone' => $person->mobile_phone,
                    'Email' => $person->email,
                    'City' => $person->city,
                    'JobTitle' => $person->job_title,
                    'BackgroundInfo' => $person->background_info,
                    'OwnerId' => $person->owner_didar_id // Include owner for Didar update
                ]
            ];
            
            $res = $this->didarApi->call('/contact/save', 'POST', $contactData);
            
            if (isset($res['error'])) {
                $resp = $res['response'] ?? [];
                $respError = is_array($resp) ? ($resp['Error'] ?? '') : '';
                // If this lead does not exist in Didar (common for imported/local-only leads), don't block local updates.
                if (is_string($respError) && stripos($respError, "Contact's data not found") !== false) {
                    $didarSyncWarning = 'Didar contact not found; local update only.';
                    Logger::logWarning('Didar contact not found; skipping remote update', [
                        'contact_id' => $contactId,
                        'http_code' => $res['http_code'] ?? null,
                        'response_error' => $respError
                    ]);
                } else {
                    throw new \Exception("Error updating lead in Didar: " . $res['error']);
                }
            }
        } else {
            $didarSyncWarning = 'Didar write disabled; local update only.';
        }

        // Update locally
        $person->last_sync = date('Y-m-d H:i:s');
        $this->personRepo->save($person);
        
        $out = ['message' => 'لید با موفقیت به‌روزرسانی شد'];
        if ($ownerChange) {
            $out['owner_change'] = $ownerChange;
        }
        if ($didarSyncWarning) {
            $out['warning'] = $didarSyncWarning;
        }
        return $out;
    }

    /**
     * Resolve duplicate person and matched phone; also return per-phone matches.
     */
     private function resolveDuplicateMatch(array $phonesToCheck, bool $includeDidarSearch = false): array
    {
        $matches = [];
        $didarMatches = [];
        $didarErrors = [];
        $seenPhones = [];

        foreach ($phonesToCheck as $p) {
            if (in_array($p, $seenPhones, true)) {
                continue;
            }
            $seenPhones[] = $p;
            $person = $this->personRepo->findByPhone($p);
            if ($person) {
                $matches[] = [
                    'phone' => $p,
                    'contact_id' => $person->didar_contact_id,
                    'owner_id' => $person->owner_didar_id,
                    'owner_name' => $this->getUserNameByDidarId($person->owner_didar_id),
                    'contact_name' => trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                    'found' => true
                ];
                continue;
            }

            // If not found locally, optionally try Didar search (with variants)
            if ($includeDidarSearch) {
                $variants = PhoneNormalizer::normalizeCandidates($p);
                if (strlen($p) === 10 && substr($p, 0, 1) === '9') {
                    $variants[] = '0' . $p; // add leading zero variant
                }
                if (strlen($p) === 11 && substr($p, 0, 1) === '0') {
                    $variants[] = substr($p, 1); // add trimmed variant
                }
                $variants = array_values(array_unique(array_filter($variants)));
                // جستجو روی MobilePhone
                $searchRes = $this->didarApi->searchContactsByPhonesDetailed(!empty($variants) ? $variants : [$p], 'MobilePhone');
                $didarContacts = $searchRes['contacts'] ?? [];
                $didarErrors = array_merge($didarErrors, $searchRes['errors'] ?? []);
                // جستجو روی Phone
                $searchResPhone = $this->didarApi->searchContactsByPhonesDetailed(!empty($variants) ? $variants : [$p], 'Phone');
                $didarContacts = array_merge($didarContacts, $searchResPhone['contacts'] ?? []);
                $didarErrors = array_merge($didarErrors, $searchResPhone['errors'] ?? []);
                foreach ($didarContacts as $c) {
                    $contactId = $c['Id'] ?? ($c['ContactId'] ?? null);
                    $ownerId = $c['OwnerId'] ?? ($c['Owner']['Id'] ?? ($c['Owner']['OwnerId'] ?? ''));
                    $displayName = $c['DisplayName'] ?? ($c['FullName'] ?? (($c['FirstName'] ?? '') . ' ' . ($c['LastName'] ?? '')));
                    $ownerNameFallback = $c['Owner']['Name'] ?? ($c['Owner']['DisplayName'] ?? '');
                    $ownerName = $this->getUserNameByDidarId($ownerId);
                    if (empty($ownerName) && !empty($ownerNameFallback)) {
                        $ownerName = $ownerNameFallback;
                    }
                    $row = [
                        'phone' => $p,
                        'contact_id' => $contactId,
                        'owner_id' => $ownerId,
                        'owner_name' => $ownerName,
                        'contact_name' => trim($displayName),
                        'found' => !empty($contactId)
                    ];
                    $matches[] = $row;
                    $didarMatches[] = $row;
                }
            }
        }

        // فقط اگر در دیتابیس محلی بر اساس شماره پیدا شده باشد، person را ست می‌کنیم
        $localPerson = null;
        $localMatchedPhone = null;
        foreach ($matches as $m) {
            if (!empty($m['found']) && !empty($m['contact_id'])) {
                $candidate = $this->personRepo->findByDidarId($m['contact_id']);
                if ($candidate) {
                    $localPerson = $candidate;
                    $localMatchedPhone = $m['phone'] ?? null;
                    break;
                }
            }
        }

        $person = $localPerson;
        $matchedPhone = $localMatchedPhone ?? ($matches[0]['phone'] ?? null);

        return [
            'person' => $person,
            'matched_phone' => $matchedPhone,
            'matches' => $matches,
            'didar_matches' => $didarMatches,
            'didar_error' => !empty($didarErrors) ? implode(' | ', array_unique($didarErrors)) : ''
        ];
    }

    /**
     * Duplicate detection only against local DB (no Didar).
     */
    private function resolveDuplicateMatchLocalOnly(array $phonesToCheck): array
    {
        $matches = [];
        $seenPhones = [];

        foreach ($phonesToCheck as $p) {
            if (in_array($p, $seenPhones, true)) {
                continue;
            }
            $seenPhones[] = $p;
            $person = $this->personRepo->findByPhone($p);
            if ($person) {
                Logger::logInfo("Duplicate phone found in resolveDuplicateMatchLocalOnly", [
                    'phone' => $p,
                    'found_didar_id' => $person->didar_contact_id,
                    'found_local_id' => $person->id,
                    'found_name' => trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? ''))
                ]);
                $ownerId = $person->owner_didar_id ?? '';
                $ownerName = $this->getUserNameByDidarId($ownerId);
                // اگر owner_name خالی است اما owner_id وجود دارد، لاگ کن
                if (empty($ownerName) && !empty($ownerId)) {
                    Logger::logError("Owner name not found in users table in resolveDuplicateMatchLocalOnly", null, [
                        'owner_id' => $ownerId,
                        'contact_id' => $person->didar_contact_id,
                        'phone' => $p
                    ]);
                }
                $matches[] = [
                    'phone' => $p,
                    'contact_id' => $person->didar_contact_id,
                    'owner_id' => $ownerId,
                    'owner_name' => $ownerName,
                    'contact_name' => trim(($person->first_name ?? '') . ' ' . ($person->last_name ?? '')),
                    'found' => true
                ];
            }
        }

        // فقط اگر در دیتابیس محلی بر اساس شماره پیدا شده باشد، person را ست می‌کنیم
        $localPerson = null;
        $localMatchedPhone = null;
        foreach ($matches as $m) {
            if (!empty($m['found']) && !empty($m['contact_id'])) {
                $candidate = $this->personRepo->findByDidarId($m['contact_id']);
                if ($candidate) {
                    $localPerson = $candidate;
                    $localMatchedPhone = $m['phone'] ?? null;
                    break;
                }
            }
        }

        $person = $localPerson;
        $matchedPhone = $localMatchedPhone ?? ($matches[0]['phone'] ?? null);

        return [
            'person' => $person,
            'matched_phone' => $matchedPhone,
            'matches' => $matches,
            'didar_matches' => [],
            'didar_error' => ''
        ];
    }

    /**
     * اطمینان از وجود رکورد محلی برای لید دیدار (حداقلی)
     */
    private function ensureLocalPersonFromMatch(string $contactId, string $ownerId, ?string $contactName, string $matchedPhone): \App\Models\Person
    {
        $existing = $this->personRepo->findByDidarId($contactId);
        if ($existing) {
            return $existing;
        }
        $nameSafe = trim((string)$contactName);
        $parts = explode(' ', $nameSafe, 2);
        $first = $parts[0] ?? '';
        $last = $parts[1] ?? ($parts[0] ?? '');
        $person = new \App\Models\Person([
            'didar_contact_id' => $contactId,
            'owner_didar_id' => $ownerId,
            'previous_owner_id' => null,
            'code' => '',
            'first_name' => $first,
            'last_name' => $last,
            'mobile_phone' => $matchedPhone,
            'contact_type' => 'Lead',
            'customer_level' => null,
            'customer_level_notes' => null,
            'register_time' => date('Y-m-d H:i:s'),
            'last_sync' => date('Y-m-d H:i:s'),
            'raw_json' => json_encode(['source' => 'didar_duplicate'], JSON_UNESCAPED_UNICODE)
        ]);
        $this->personRepo->save($person);
        return $person;
    }

    /**
     * Add referral record to database
     */
    private function addReferralRecord(string $contactId, string $referrerId, string $crmSpecialistId, string $note = ''): void
    {
        try {
            $db = $this->personRepo->getDb();
            $pdo = $db->getPdo();

            // Check if referrals table exists
            $tableExists = $db->hasColumn('referrals', 'id');
            if (!$tableExists) {
                Logger::logWarning("Referrals table does not exist, creating it");
                // Try to create the table
                $createSql = "CREATE TABLE IF NOT EXISTS referrals (
                    id INT PRIMARY KEY AUTO_INCREMENT,
                    contact_didar_id VARCHAR(255) NOT NULL,
                    referrer_didar_id VARCHAR(255),
                    note TEXT,
                    phones TEXT,
                    status VARCHAR(50) DEFAULT 'pending',
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    INDEX idx_contact_didar_id (contact_didar_id),
                    INDEX idx_referrer_didar_id (referrer_didar_id),
                    FOREIGN KEY (contact_didar_id) REFERENCES persons(didar_contact_id) ON DELETE CASCADE ON UPDATE CASCADE,
                    FOREIGN KEY (referrer_didar_id) REFERENCES users(didar_user_id) ON DELETE SET NULL ON UPDATE CASCADE
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
                $pdo->exec($createSql);
                Logger::logInfo("Referrals table created successfully");
            }

            // Get person phones for referral record
            $person = $this->personRepo->findByDidarId($contactId);
            $phones = [];
            if ($person) {
                if (!empty($person->mobile_phone)) $phones[] = $person->mobile_phone;
                if (!empty($person->secondary_mobile_phone)) $phones[] = $person->secondary_mobile_phone;
                if (!empty($person->mobile_phone_3)) $phones[] = $person->mobile_phone_3;
                if (!empty($person->mobile_phone_4)) $phones[] = $person->mobile_phone_4;
            }

            $phonesJson = json_encode(array_filter($phones), JSON_UNESCAPED_UNICODE);
            $fullNote = 'خرید قبلی - ارجاع خودکار به کارشناس CRM';
            if (!empty($note)) {
                $fullNote .= ' (شماره خرید قبلی: ' . $note . ')';
            }

            $stmt = $pdo->prepare("INSERT INTO referrals (contact_didar_id, referrer_didar_id, note, phones, status) VALUES (?, ?, ?, ?, 'pending')");
            $referrerIdForInsert = $referrerId;
            if (!empty($referrerIdForInsert)) {
                $check = $pdo->prepare("SELECT 1 FROM users WHERE didar_user_id = ? LIMIT 1");
                $check->execute([$referrerIdForInsert]);
                if (!$check->fetchColumn()) {
                    $referrerIdForInsert = null;
                }
            }

            $stmt->execute([$contactId, $referrerIdForInsert, $fullNote, $phonesJson]);

            Logger::logInfo("Referral record added", [
                'contact_id' => $contactId,
                'referrer_id' => $referrerId,
                'crm_specialist_id' => $crmSpecialistId,
                'note' => $fullNote
            ]);
        } catch (\Exception $e) {
            Logger::logError("Failed to add referral record", $e, [
                'contact_id' => $contactId,
                'referrer_id' => $referrerId,
                'crm_specialist_id' => $crmSpecialistId
            ]);
            // Don't throw exception - continue with update
        }
    }

    /**
     * Transfer lead owner and propagate to deals.
     */
    public function transferLeadOwner(string $contactId, string $newOwnerId): array
    {
        if (empty($newOwnerId) || $newOwnerId === 'undefined') {
            throw new \Exception("مسئول جدید در سیستم یافت نشد");
        }
        $person = $this->personRepo->findByDidarId($contactId);
        if (!$person) {
            throw new \Exception("لید یافت نشد");
        }

        // Verify new owner exists
        $db = $this->personRepo->getDb();
        $pdo = $db->getPdo();
        $hasDisplayName = $db->hasColumn('users', 'display_name');
        $ownerCheckSql = $hasDisplayName
            ? "SELECT didar_user_id, display_name FROM users WHERE didar_user_id = ? LIMIT 1"
            : "SELECT didar_user_id FROM users WHERE didar_user_id = ? LIMIT 1";
        $ownerCheck = $pdo->prepare($ownerCheckSql);
        $ownerCheck->execute([$newOwnerId]);
        $newOwner = $ownerCheck->fetch(\PDO::FETCH_ASSOC);
        if (!$newOwner) {
            throw new \Exception("مسئول جدید در سیستم یافت نشد");
        }

        $oldOwnerId = $person->owner_didar_id;
        if ($oldOwnerId === $newOwnerId) {
            return [
                'message' => 'مسئول بدون تغییر است',
                'old_owner_id' => $oldOwnerId,
                'new_owner_id' => $newOwnerId,
                'old_owner_name' => $this->getUserNameByDidarId($oldOwnerId),
                'new_owner_name' => $this->getUserNameByDidarId($newOwnerId),
            ];
        }

        try {
            $pdo->beginTransaction();
            $this->personRepo->updateOwnerWithPrevious($contactId, $newOwnerId, $oldOwnerId);
            $this->dealRepo->updateOwnerByContact($contactId, $newOwnerId);
            $this->insertOwnerHistoryRow($pdo, (int)($person->id ?? 0), $contactId, $oldOwnerId, $newOwnerId, 'manual', 'Lead owner transfer');
            $pdo->commit();
        } catch (\Exception $e) {
            $pdo->rollBack();
            Logger::logError('Failed to transfer lead owner', $e, [
                'contact_id' => $contactId,
                'old_owner_id' => $oldOwnerId,
                'new_owner_id' => $newOwnerId
            ]);
            throw new \Exception("انتقال مسئول لید با خطا مواجه شد");
        }

        return [
            'message' => 'مسئول لید و معاملات مرتبط منتقل شد',
            'old_owner_id' => $oldOwnerId,
            'new_owner_id' => $newOwnerId,
            'old_owner_name' => $this->getUserNameByDidarId($oldOwnerId),
            'new_owner_name' => $this->getUserNameByDidarId($newOwnerId),
        ];
    }

    private function insertOwnerHistoryRow(\PDO $pdo, int $personId, string $contactId, ?string $oldOwnerId, ?string $newOwnerId, string $source, string $reason): void
    {
        try {
            $tableExists = $pdo->query("SHOW TABLES LIKE 'lead_owner_history'")->fetchColumn();
            if (!$tableExists) {
                return;
            }
            $stmt = $pdo->prepare("INSERT INTO lead_owner_history
                (person_id, contact_didar_id, from_owner_didar_id, from_owner_name, to_owner_didar_id, to_owner_name,
                 changed_by_user_id, changed_by_didar_id, changed_by_name, change_source, reason, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())");
            $stmt->execute([
                $personId > 0 ? $personId : null,
                $contactId,
                $oldOwnerId ?: null,
                $oldOwnerId ? $this->getUserNameByDidarId($oldOwnerId) : null,
                $newOwnerId ?: null,
                $newOwnerId ? $this->getUserNameByDidarId($newOwnerId) : null,
                $_SESSION['user_id'] ?? null,
                $_SESSION['didar_id'] ?? null,
                $_SESSION['display_name'] ?? $_SESSION['username'] ?? null,
                $source,
                $reason,
            ]);
        } catch (\Throwable $e) {
            Logger::logWarning('Failed to insert lead owner history row', [
                'contact_id' => $contactId,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Deduplicate matches array by contact_id to avoid showing duplicate entries
     */
    private function dedupeMatchesByContactId(array $matches): array
    {
        $seen = [];
        $out = [];
        foreach ($matches as $m) {
            $cid = $m['contact_id'] ?? null;
            if ($cid) {
                if (isset($seen[$cid])) {
                    continue;
                }
                $seen[$cid] = true;
            }
            $out[] = $m;
        }
        return $out;
    }

    private function getUserNameByDidarId($didarId): string
    {
        if (empty($didarId)) {
            return '';
        }
        $db = $this->personRepo->getDb();
        $pdo = $db->getPdo();
        $hasDisplayName = $db->hasColumn('users', 'display_name');
        $field = $hasDisplayName ? 'display_name' : "CONCAT(first_name, ' ', last_name)";
        $stmt = $pdo->prepare("SELECT $field as name FROM users WHERE didar_user_id = ? LIMIT 1");
        $stmt->execute([$didarId]);
        $name = $stmt->fetchColumn();
        return $name ?: '';
    }
}
