<?php

namespace App\Services;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Ramsey\Uuid\Uuid;
use Torann\GeoIP\Facades\GeoIP;

class VisitorService
{
    const COOKIE_NAME = 'store_visitor_id';

    const CACHE_PREFIX = 'VISITORDETAIL_';

    const VISITOR_BASKET_PREFIX = 'VISITORBASKET_';

    const CACHE_TTL = 60 * 24 * 30; // 30 gün

    /**
     * Ziyaretçi ID'sini al veya oluştur
     */
    public function getOrCreateVisitorId(Request $request): string
    {
        $encryptedVisitorId = $request->cookie(self::COOKIE_NAME);

        if (! $encryptedVisitorId) {
            $visitorId = $this->generateVisitorId();
        } else {
            // Cookie'den gelen encrypted ID'yi decrypt et
            try {
                $visitorId = decrypt($encryptedVisitorId);
            } catch (\Exception $e) {
                \Log::warning('Failed to decrypt visitor ID from cookie', [
                    'encryptedId' => $encryptedVisitorId,
                    'error' => $e->getMessage(),
                ]);
                $visitorId = $this->generateVisitorId();
            }
        }

        return $visitorId;
    }

    /**
     * Ziyaretçi bilgilerini al
     */
    public function getVisitorDetails(string $visitorId): array
    {
        return Cache::get(self::CACHE_PREFIX.$visitorId, []);
    }

    /**
     * Ziyaretçi bilgilerini güncelle
     */
    public function updateVisitorDetails(string $visitorId, array $data): void
    {
        $existingData = $this->getVisitorDetails($visitorId);
        $updatedData = array_merge($existingData, $data);

        Cache::put(
            self::CACHE_PREFIX.$visitorId,
            $updatedData,
            self::CACHE_TTL
        );
    }

    /**
     * Affiliate ID'sini kaydet
     */
    public function setAffiliateId(string $visitorId, string $affiliateId): void
    {
        $this->updateVisitorDetails($visitorId, [
            'affiliateId' => $affiliateId,
            'affiliateSetAt' => now()->toISOString(),
        ]);
    }

    /**
     * IP adresini kaydet
     */
    public function setIpAddress(string $visitorId, string $ipAddress): void
    {
        $this->updateVisitorDetails($visitorId, [
            'ipAddress' => $ipAddress,
            'lastSeenAt' => now()->toISOString(),
        ]);
    }

    /**
     * IP adresinden ülke bilgisini al ve kaydet
     */
    public function setCountryFromIp(string $visitorId, string $ipAddress): void
    {
        // Check if we already have geo data for this visitor (avoid repeated API calls)
        $existingDetails = $this->getVisitorDetails($visitorId);
        if (!empty($existingDetails['countryCode']) && !empty($existingDetails['ipAddress']) && $existingDetails['ipAddress'] === $ipAddress) {
            // Already have geo data for this IP, skip API call
            return;
        }

        // Local IP'ler için mock veri
        if ($this->isLocalIp($ipAddress)) {

            $this->updateVisitorDetails($visitorId, [
                'country' => __('admin/common.services_visitor_local_country'),
                'countryCode' => 'TR',
                'city' => __('admin/common.services_visitor_local_city'),
                'state' => __('admin/common.services_visitor_local_state'),
                'timezone' => __('admin/common.services_visitor_local_timezone'),
                'continent' => 'AS',
                'currency' => 'TRY',
                'geoData' => [
                    'lat' => 40.7128,
                    'lon' => -74.0060,
                    'postal_code' => '10001',
                ],
            ]);

            return;
        }

        try {
            $location = GeoIP::getLocation($ipAddress);

            $this->updateVisitorDetails($visitorId, [
                'country' => $location->country ?? null,
                'countryCode' => $location->iso_code ?? null,
                'city' => $location->city ?? null,
                'state' => $location->state ?? null,
                'timezone' => $location->timezone ?? null,
                'continent' => $location->continent ?? null,
                'currency' => $location->currency ?? null,
                'geoData' => [
                    'lat' => $location->lat ?? null,
                    'lon' => $location->lon ?? null,
                    'postal_code' => $location->postal_code ?? null,
                ],
            ]);
        } catch (\Exception $e) {

            // Fallback: Default location kullan
            $this->updateVisitorDetails($visitorId, [
                'country' => 'United States',
                'countryCode' => 'US',
                'city' => 'New Haven',
                'state' => 'CT',
                'timezone' => 'America/New_York',
                'continent' => 'NA',
                'currency' => 'USD',
                'geoData' => [
                    'lat' => 41.31,
                    'lon' => -72.92,
                    'postal_code' => '06510',
                ],
            ]);
        }
    }

    /**
     * IP adresinin local olup olmadığını kontrol et
     */
    private function isLocalIp(string $ipAddress): bool
    {
        // Local IP aralıkları
        $localRanges = [
            '127.0.0.0/8',      // 127.0.0.0 - 127.255.255.255
            '10.0.0.0/8',       // 10.0.0.0 - 10.255.255.255
            '172.16.0.0/12',    // 172.16.0.0 - 172.31.255.255
            '192.168.0.0/16',   // 192.168.0.0 - 192.168.255.255
        ];

        foreach ($localRanges as $range) {
            if ($this->ipInRange($ipAddress, $range)) {
                return true;
            }
        }

        return false;
    }

    /**
     * IP adresinin belirtilen aralıkta olup olmadığını kontrol et
     */
    private function ipInRange(string $ip, string $range): bool
    {
        if (strpos($range, '/') === false) {
            return $ip === $range;
        }

        [$subnet, $bits] = explode('/', $range);
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - $bits);
        $subnet &= $mask;

        return ($ip & $mask) === $subnet;
    }

    /**
     * Browser dil bilgisini kaydet
     */
    public function setBrowserLanguage(string $visitorId, string $language): void
    {
        $this->updateVisitorDetails($visitorId, [
            'browserLanguage' => $language,
            'languageDetectedAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın tercih ettiği dili kaydet
     */
    public function setVisitorPreferredLanguage(string $visitorId, string $language): void
    {
        $this->updateVisitorDetails($visitorId, [
            'preferredLanguage' => $language,
            'languageSetAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın belirlenen dilini kaydet
     */
    public function setVisitorLanguage(string $visitorId, string $language): void
    {
        $this->updateVisitorDetails($visitorId, [
            'language' => $language,
            'languageDeterminedAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın tercih ettiği dili al
     */
    public function getVisitorPreferredLanguage(string $visitorId): ?string
    {
        $details = $this->getVisitorDetails($visitorId);

        return $details['preferredLanguage'] ?? null;
    }

    /**
     * Visitor için uygun dili belirle (fallback sistemi)
     */
    public function determineVisitorLanguage(string $visitorId): string
    {
        // 1. Önce visitor'ın tercih ettiği dili kontrol et
        $preferredLanguage = $this->getVisitorPreferredLanguage($visitorId);
        if ($preferredLanguage && $this->isLanguageSupported($preferredLanguage)) {
            return $preferredLanguage;
        }

        // 2. Browser dilini kontrol et
        $browserLanguage = $this->getVisitorDetails($visitorId)['browserLanguage'] ?? null;
        if ($browserLanguage && $this->isLanguageSupported($browserLanguage)) {
            return $browserLanguage;
        }

        // 3. IP'den ülke koduna göre dil belirle
        $countryCode = $this->getVisitorDetails($visitorId)['countryCode'] ?? null;
        if ($countryCode) {
            $countryLanguage = $this->getLanguageByCountryCode($countryCode);
            if ($countryLanguage && $this->isLanguageSupported($countryLanguage)) {
                return $countryLanguage;
            }
        }

        // 4. Varsayılan site dilini kullan
        return $this->getDefaultSiteLanguage();
    }

    /**
     * Dil destekleniyor mu kontrol et
     */
    private function isLanguageSupported(string $languageCode): bool
    {
        return \App\Models\Language::active()
            ->where('a2_iso', $languageCode)
            ->exists();
    }

    /**
     * Ülke koduna göre dil belirle
     */
    private function getLanguageByCountryCode(string $countryCode): ?string
    {
        $countryLanguageMap = [
            'TR' => 'tr',
            'US' => 'en',
            'GB' => 'en',
            'DE' => 'de',
            'FR' => 'fr',
            'ES' => 'es',
            'IT' => 'it',
            'NL' => 'nl',
            'RU' => 'ru',
            'CN' => 'zh',
            'JP' => 'ja',
            'KR' => 'ko',
            'AR' => 'ar',
            'BR' => 'pt',
            'MX' => 'es',
            'CA' => 'en',
            'AU' => 'en',
            'IN' => 'en',
        ];

        return $countryLanguageMap[$countryCode] ?? null;
    }

    /**
     * Varsayılan site dilini al
     */
    private function getDefaultSiteLanguage(): string
    {
        $defaultLanguage = \App\Models\Language::where('site_default', true)
            ->where('status', true)
            ->value('a2_iso');

        return $defaultLanguage ?: config('app.locale', 'en');
    }

    // ========================================
    // CURRENCY YÖNETİMİ
    // ========================================

    /**
     * Visitor'ın tercih ettiği para birimini kaydet
     */
    public function setVisitorPreferredCurrency(string $visitorId, string $currency): void
    {
        $this->updateVisitorDetails($visitorId, [
            'preferredCurrency' => $currency,
            'currencySetAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın belirlenen dilini kaydet (determined language)
     */
    public function setVisitorDeterminedLanguage(string $visitorId, string $language): void
    {
        $this->updateVisitorDetails($visitorId, [
            'determined_language' => $language,
            'languageDeterminedAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın belirlenen para birimini kaydet (determined currency)
     */
    public function setVisitorDeterminedCurrency(string $visitorId, string $currency): void
    {
        $this->updateVisitorDetails($visitorId, [
            'determined_currency' => $currency,
            'currencyDeterminedAt' => now()->toISOString(),
        ]);
    }

    /**
     * Visitor'ın tercih ettiği para birimini al
     */
    public function getVisitorPreferredCurrency(string $visitorId): ?string
    {
        $details = $this->getVisitorDetails($visitorId);

        return $details['preferredCurrency'] ?? null;
    }

    /**
     * Visitor için uygun para birimini belirle (fallback sistemi)
     */
    public function determineVisitorCurrency(string $visitorId): string
    {
        // 1. Önce visitor'ın tercih ettiği para birimini kontrol et
        $preferredCurrency = $this->getVisitorPreferredCurrency($visitorId);
        if ($preferredCurrency && $this->isCurrencySupported($preferredCurrency)) {
            return $preferredCurrency;
        }

        // 2. GeoIP'den gelen para birimi bilgisini kontrol et
        $geoCurrency = $this->getVisitorDetails($visitorId)['currency'] ?? null;
        if ($geoCurrency && $this->isCurrencySupported($geoCurrency)) {
            return $geoCurrency;
        }

        // 3. IP'den ülke koduna göre para birimi belirle
        $countryCode = $this->getVisitorDetails($visitorId)['countryCode'] ?? null;
        if ($countryCode) {
            $countryCurrency = $this->getCurrencyByCountryCode($countryCode);
            if ($countryCurrency && $this->isCurrencySupported($countryCurrency)) {
                return $countryCurrency;
            }
        }

        // 4. Varsayılan site para birimini kullan
        return $this->getDefaultSiteCurrency();
    }

    /**
     * Para birimi destekleniyor mu kontrol et
     */
    private function isCurrencySupported(string $currencyCode): bool
    {
        return \App\Models\Currency::where('status', true)
            ->where('code', $currencyCode)
            ->exists();
    }

    /**
     * Ülke koduna göre para birimi belirle
     */
    private function getCurrencyByCountryCode(string $countryCode): ?string
    {
        return \App\Models\Currency::getCodeByCountryCode($countryCode);
    }

    /**
     * Varsayılan site para birimini al
     */
    private function getDefaultSiteCurrency(): string
    {
        return \App\Models\Currency::getDefaultCode();
    }

    /**
     * Request'ten browser dil bilgisini çıkar
     */
    public function extractBrowserLanguage(Request $request): ?string
    {
        $acceptLanguage = $request->header('Accept-Language');

        if (! $acceptLanguage) {
            return null;
        }

        // Accept-Language header'ını parse et
        $languages = [];
        $parts = explode(',', $acceptLanguage);

        foreach ($parts as $part) {
            $part = trim($part);
            if (strpos($part, ';') !== false) {
                [$lang, $q] = explode(';', $part, 2);
                $q = floatval(str_replace('q=', '', $q));
            } else {
                $lang = $part;
                $q = 1.0;
            }

            $languages[$lang] = $q;
        }

        // En yüksek kalite skoruna sahip dili al
        arsort($languages);
        $preferredLanguage = array_key_first($languages);

        // Sadece dil kodunu al (örn: 'en-US' -> 'en')
        return explode('-', $preferredLanguage)[0];
    }

    /**
     * Ziyaretçi oluşturulma tarihini kaydet
     */
    public function setCreatedAt(string $visitorId): void
    {
        $existingData = $this->getVisitorDetails($visitorId);

        if (! isset($existingData['createdAt'])) {
            $this->updateVisitorDetails($visitorId, [
                'createdAt' => now()->toISOString(),
            ]);
        }
    }

    /**
     * İlk landing bilgisini kaydet (attribution & entry source tracking).
     * Sadece BİR KEZ yazılır (entry_at zaten varsa overwrite etmez).
     *
     * Saklanan alanlar:
     *   - entry_path     : '/urunler/qa-ssl' (ilk URL path)
     *   - entry_url      : full URL (?query dahil)
     *   - entry_referer  : HTTP referer (Google, başka site, vb)
     *   - entry_query    : ?utm_source=... gibi attribution parametreleri
     *   - entry_route    : Laravel route name (örn 'store.products.show')
     *   - entry_at       : ISO timestamp
     *
     * Use case: "Bu ziyaretçi siteye nereden geldi?" attribution analytics.
     */
    public function setEntryInfo(string $visitorId, Request $request): void
    {
        $existingData = $this->getVisitorDetails($visitorId);

        if (isset($existingData['entry_at'])) {
            return; // Idempotent — first-touch attribution korunur
        }

        $route = $request->route();
        $routeName = $route ? $route->getName() : null;

        $this->updateVisitorDetails($visitorId, [
            'entry_path'    => '/'.ltrim($request->path(), '/'),
            'entry_url'     => $request->fullUrl(),
            'entry_referer' => $request->headers->get('referer'),
            'entry_query'   => $request->query() ?: null,
            'entry_route'   => $routeName,
            'entry_at'      => now()->toISOString(),
        ]);
    }

    /**
     * Ziyaretçi ID'sini cookie'ye kaydet
     */
    public function setVisitorCookie(string $visitorId)
    {
        return cookie(
            self::COOKIE_NAME,
            encrypt($visitorId), // Visitor ID'yi encrypt et
            self::CACHE_TTL / 60, // Cookie TTL dakika cinsinden
            '/', // Path
            null, // Domain
            false, // Secure
            true, // HttpOnly
            false, // Raw
            'strict' // SameSite
        );
    }

    /**
     * Request'ten affiliate ID'sini çıkar
     */
    public function extractAffiliateId(Request $request): ?string
    {
        return $request->query('affId');
    }

    /**
     * Ziyaretçi istatistiklerini al
     */
    public function getVisitorStats(string $visitorId): array
    {
        $details = $this->getVisitorDetails($visitorId);

        return [
            'visitorId' => $visitorId,
            'hasAffiliate' => ! empty($details['affiliateId']),
            'affiliateId' => $details['affiliateId'] ?? null,
            'createdAt' => $details['createdAt'] ?? null,
            'lastSeenAt' => $details['lastSeenAt'] ?? null,
            'ipAddress' => $details['ipAddress'] ?? null,
        ];
    }

    /**
     * Benzersiz ziyaretçi ID'si oluştur
     */
    private function generateVisitorId(): string
    {
        return Uuid::uuid4()->toString();
    }

    /**
     * Ziyaretçi bilgilerini temizle (test amaçlı)
     */
    public function clearVisitorData(string $visitorId): void
    {
        Cache::forget(self::CACHE_PREFIX.$visitorId);
    }

    /**
     * Tüm ziyaretçi verilerini temizle (test amaçlı)
     */
    public function clearAllVisitorData(): void
    {
        // Bu metod sadece test ortamında kullanılmalı
        if (app()->environment('testing')) {
            $keys = Cache::getRedis()->keys(self::CACHE_PREFIX.'*');
            if (! empty($keys)) {
                Cache::getRedis()->del($keys);
            }
        }
    }

    // ========================================
    // VISITOR INFO YÖNETİMİ
    // ========================================

    /**
     * Visitor bilgilerini al (kullanıcı ID ile harmanlanmış)
     */
    public function getVisitorInfo(string $visitorId): array
    {
        $details = $this->getVisitorDetails($visitorId);
        $info = Cache::get(self::CACHE_PREFIX.$visitorId, []);

        return array_merge($details, $info);
    }

    /**
     * Visitor bilgilerini güncelle
     */
    public function updateVisitorInfo(string $visitorId, array $data): void
    {
        $existingInfo = Cache::get(self::CACHE_PREFIX.$visitorId, []);
        $updatedInfo = array_merge($existingInfo, $data);

        Cache::put(
            self::CACHE_PREFIX.$visitorId,
            $updatedInfo,
            self::CACHE_TTL
        );
    }

    /**
     * Logged-in kullanıcı ID'sini visitor'a bağla
     */
    public function linkUserToVisitor(string $visitorId, int $userId): void
    {
        $this->updateVisitorInfo($visitorId, [
            'userId' => $userId,
            'linkedAt' => now()->toISOString(),
        ]);

        // USER_VISITOR_INDEX tek otorite — tek yazıcı burası (saf Cache).
        \App\Helpers\VisitorHelper::addToUserVisitorIndex($userId, $visitorId);
    }

    /**
     * Visitor'ın bağlı kullanıcı ID'sini al
     */
    public function getVisitorUserId(string $visitorId): ?int
    {
        $info = Cache::get(self::CACHE_PREFIX.$visitorId, []);

        return $info['userId'] ?? null;
    }

    /**
     * Kullanıcı ID'sine göre visitor'ı bul
     */
    public function findVisitorByUserId(int $userId): ?string
    {
        $index = Cache::get(
            \App\Helpers\VisitorHelper::USER_VISITOR_INDEX_KEY, []
        );
        // Non-atomic map (TrackVisitor hot-path) — yarış benign + self-healing (linkUserToVisitor yeniden kurar).
        $candidate = $index[$userId] ?? $index[(string) $userId] ?? null;
        if (! $candidate) {
            return null;
        }

        // Sepeti boş/expire ise self-heal: indeksten düş, null dön.
        $basket = \App\Helpers\VisitorHelper::getVisitorBasket($candidate);
        if (empty($basket['items'] ?? [])) {
            \App\Helpers\VisitorHelper::removeFromUserVisitorIndex($userId);
            return null;
        }

        return $candidate;
    }

    /**
     * Tüm visitor cache'lerini temizle (test amaçlı)
     */
    public function clearAllVisitorCaches(): void
    {
        if (app()->environment('testing')) {
            $patterns = [
                self::CACHE_PREFIX.'*',
                self::VISITOR_BASKET_PREFIX.'*',
            ];

            foreach ($patterns as $pattern) {
                $keys = Cache::getRedis()->keys($pattern);
                if (! empty($keys)) {
                    Cache::getRedis()->del($keys);
                }
            }
        }
    }
}
