<?php

namespace App\Services;

use App\Models\DynamicDnsClient;
use App\Models\DynamicDnsUpdateLog;
use App\Models\DnsRecord;
use App\Models\DnsZone;
use App\Services\PowerDNSService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

class DynamicDnsService
{
    /**
     * Actualizează înregistrarea DNS cu IP-ul nou
     */
    public function updateDnsRecord(DynamicDnsClient $client, string $newIp, ?string $requestIp = null, ?string $userAgent = null): array
    {
        try {
            // Verifică dacă IP-ul s-a schimbat
            if (!$client->hasIpChanged($newIp)) {
                // Loghează chiar și când IP-ul nu s-a schimbat
                $this->logUpdate($client, $client->current_ip, $newIp, $requestIp, $userAgent, 'nochg', 'IP neschimbat');

                return [
                    'success' => true,
                    'message' => 'IP unchanged',
                    'hostname' => $client->hostname,
                    'ip' => $newIp,
                    'updated' => false,
                ];
            }

            // Determină tipul de înregistrare (A pentru IPv4, AAAA pentru IPv6)
            $recordType = $this->getRecordType($newIp);
            $recordName = $this->extractRecordName($client->hostname, $client->dnsZone->domain);

            // Găsește înregistrarea DNS existentă de tipul corect
            $record = DnsRecord::where('dns_zone_id', $client->dnsZone->id)
                ->where('name', $recordName)
                ->where('type', $recordType)
                ->first();

            $oldIp = null;

            if ($record) {
                // Actualizează înregistrarea existentă
                $oldIp = $record->content;
                $record->update([
                    'content' => $newIp,
                    'is_active' => true,
                ]);
            } else {
                // Creează înregistrare nouă
                $record = DnsRecord::create([
                    'dns_zone_id' => $client->dnsZone->id,
                    'name' => $recordName,
                    'type' => $recordType,
                    'content' => $newIp,
                    'ttl' => $client->ttl,
                    'priority' => null,
                    'is_active' => true,
                ]);
            }

            // Dacă tipul de IP s-a schimbat (IPv4 la IPv6 sau invers), dezactivează tipul vechi
            $oppositeType = $recordType === 'A' ? 'AAAA' : 'A';
            DnsRecord::where('dns_zone_id', $client->dnsZone->id)
                ->where('name', $recordName)
                ->where('type', $oppositeType)
                ->update(['is_active' => false]);

            // Sincronizează pe toate serverele de nume atribuite acestei zone
            $this->syncRecordToNameServers($client->dnsZone, $record);

            // Actualizează clientul
            $client->updateIp($newIp);

            // Loghează actualizarea cu succes
            $this->logUpdate($client, $oldIp, $newIp, $requestIp, $userAgent, 'success', 'IP actualizat cu succes');

            // Trimite notificare dacă este activată
            if ($client->notify_on_change) {
                $this->sendChangeNotification($client, $oldIp, $newIp);
            }

            Log::info("DynDNS updated: {$client->hostname} from {$oldIp} to {$newIp}");

            return [
                'success' => true,
                'message' => 'IP updated successfully',
                'hostname' => $client->hostname,
                'old_ip' => $oldIp,
                'new_ip' => $newIp,
                'updated' => true,
                'record_type' => $recordType,
            ];

        } catch (\Exception $e) {
            // Loghează eroarea
            $this->logUpdate($client, $client->current_ip, $newIp, $requestIp, $userAgent, 'error', $e->getMessage());

            Log::error("DynDNS update failed for {$client->hostname}: " . $e->getMessage());

            return [
                'success' => false,
                'message' => 'Update failed: ' . $e->getMessage(),
                'hostname' => $client->hostname,
            ];
        }
    }

    /**
     * Loghează o actualizare DynDNS
     */
    protected function logUpdate(
        DynamicDnsClient $client,
        ?string $oldIp,
        string $newIp,
        ?string $requestIp,
        ?string $userAgent,
        string $status,
        ?string $message
    ): void {
        try {
            DynamicDnsUpdateLog::create([
                'dynamic_dns_client_id' => $client->id,
                'old_ip' => $oldIp,
                'new_ip' => $newIp,
                'request_ip' => $requestIp,
                'user_agent' => $userAgent ? substr($userAgent, 0, 255) : null,
                'status' => $status,
                'message' => $message,
            ]);
        } catch (\Exception $e) {
            Log::error("Failed to log DynDNS update: " . $e->getMessage());
        }
    }

    /**
     * Extrage numele înregistrării din hostname
     */
    protected function extractRecordName(string $hostname, string $domain): string
    {
        // Elimină domeniul din hostname
        // acasa.example.com + example.com = acasa
        // example.com + example.com = @

        if ($hostname === $domain) {
            return '@';
        }

        return str_replace('.' . $domain, '', $hostname);
    }

    /**
     * Determină tipul de înregistrare pe baza formatului IP
     */
    protected function getRecordType(string $ip): string
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return 'A';
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return 'AAAA';
        }

        throw new \InvalidArgumentException('Invalid IP address format');
    }

    /**
     * Trimite notificare la schimbarea IP-ului
     */
    protected function sendChangeNotification(DynamicDnsClient $client, ?string $oldIp, string $newIp): void
    {
        try {
            // Trimite email către utilizator
            \Mail::to($client->user->email)->send(
                new \App\Mail\DynDnsIpChanged($client, $oldIp ?? 'N/A', $newIp)
            );

            Log::info("DynDNS IP change notification sent to {$client->user->email} for {$client->hostname}: {$oldIp} -> {$newIp}");
        } catch (\Exception $e) {
            Log::error("Failed to send DynDNS notification email: " . $e->getMessage());
        }
    }

    /**
     * Obține IP-ul clientului din request
     */
    public function getClientIp($request): string
    {
        // Verifică IP-ul în diferite header-e (pentru proxy/load balancers)
        $headers = [
            'HTTP_CF_CONNECTING_IP',    // Cloudflare
            'HTTP_X_FORWARDED_FOR',     // Header standard proxy
            'HTTP_X_REAL_IP',           // Nginx proxy
            'HTTP_CLIENT_IP',           // Alte proxy-uri
            'REMOTE_ADDR',              // Conexiune directă
        ];

        $validIpv4 = null;
        $validIpv6 = null;

        foreach ($headers as $header) {
            if ($request->server($header)) {
                $ip = $request->server($header);

                // Procesează IP-uri separate prin virgulă (X-Forwarded-For poate avea multiple)
                if (strpos($ip, ',') !== false) {
                    $ips = explode(',', $ip);
                    foreach ($ips as $singleIp) {
                        $singleIp = trim($singleIp);
                        if ($this->isPublicIp($singleIp)) {
                            // Prioritizează IPv4
                            if (filter_var($singleIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                                return $singleIp;
                            } elseif (!$validIpv6) {
                                $validIpv6 = $singleIp;
                            }
                        }
                    }
                } else {
                    if ($this->isPublicIp($ip)) {
                        // Prioritizează IPv4
                        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                            return $ip;
                        } elseif (!$validIpv6) {
                            $validIpv6 = $ip;
                        }
                    }
                }
            }
        }

        // Returnează IPv4 dacă este găsit, altfel IPv6, altfel fallback la IP-ul din request
        if ($validIpv4) {
            return $validIpv4;
        }

        if ($validIpv6) {
            return $validIpv6;
        }

        // Ultima soluție - verifică request->ip() dar validează că nu este localhost
        $fallbackIp = $request->ip();
        if ($this->isPublicIp($fallbackIp)) {
            return $fallbackIp;
        }

        // Dacă am ajuns aici, nu am putut detecta un IP public valid din header-e
        // Încearcă să obțină IP-ul public de la un serviciu extern (pentru scenarii localhost/NAT)
        Log::info("DynDNS: Niciun IP public în headers, se încearcă detecția IP-ului extern");

        try {
            $externalIp = $this->getPublicIpFromExternalService();
            if ($externalIp && $this->isPublicIp($externalIp)) {
                Log::info("DynDNS: External IP detected successfully: {$externalIp}");
                return $externalIp;
            }
        } catch (\Exception $e) {
            Log::warning("DynDNS: Failed to get external IP: " . $e->getMessage());
        }

        // Dacă am ajuns aici, nu am putut detecta un IP public valid
        // De obicei înseamnă că request-ul este de pe localhost
        throw new \RuntimeException('Nu s-a putut detecta adresa IP publică. Vă rugăm specificați IP-ul manual folosind parametrul &ip=.');
    }

    /**
     * Verifică dacă IP-ul este public (nu privat/rezervat/localhost)
     */
    protected function isPublicIp(string $ip): bool
    {
        // Mai întâi validează că este un IP valid
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }

        // Filtrează IP-urile localhost
        if ($ip === '::1' || $ip === '127.0.0.1' || strpos($ip, '127.') === 0) {
            return false;
        }

        // Filtrează rangurile private și rezervate
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return false;
        }

        return true;
    }

    /**
     * Validează adresa IP
     */
    public function isValidIp(string $ip): bool
    {
        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) !== false;
    }

    /**
     * Obține IP-ul public de la serviciu extern
     * Încearcă mai multe servicii pentru redundanță
     */
    protected function getPublicIpFromExternalService(): ?string
    {
        // Lista serviciilor de detecție IP fiabile
        $services = [
            'https://api.ipify.org',
            'https://icanhazip.com',
            'https://ifconfig.me/ip',
            'https://ipv4.adresameaip.ro',
        ];

        foreach ($services as $service) {
            try {
                $response = \Illuminate\Support\Facades\Http::timeout(3)->get($service);

                if ($response->successful()) {
                    $ip = trim($response->body());

                    // Validează că este un IPv4 valid (prioritizează IPv4)
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                        return $ip;
                    }
                }
            } catch (\Exception $e) {
                // Încearcă următorul serviciu
                continue;
            }
        }

        return null;
    }

    /**
     * Sincronizează înregistrarea DNS pe toate serverele de nume din zonă
     */
    protected function syncRecordToNameServers(DnsZone $zone, DnsRecord $record): void
    {
        try {
            // Obține toate serverele de nume pentru această zonă
            $nameServers = $zone->nameServers()->where('is_active', true)->get();

            if ($nameServers->isEmpty()) {
                Log::warning("No active nameservers found for zone: {$zone->domain}");
                return;
            }

            // Pregătește RRsets pentru API-ul PowerDNS
            $rrsets = [
                'rrsets' => [
                    [
                        'name' => $record->name === '@' ? $zone->domain . '.' : $record->name . '.' . $zone->domain . '.',
                        'type' => $record->type,
                        'ttl' => $record->ttl,
                        'changetype' => 'REPLACE',
                        'records' => [
                            [
                                'content' => $record->content,
                                'disabled' => !$record->is_active,
                            ]
                        ]
                    ]
                ]
            ];

            // Sincronizează pe fiecare server de nume
            foreach ($nameServers as $nameServer) {
                try {
                    $powerDNS = new PowerDNSService(
                        $nameServer->api_url,
                        $nameServer->api_key,
                        $nameServer->server_id
                    );

                    $zoneName = rtrim($zone->domain, '.') . '.';
                    $response = $powerDNS->updateZone($zoneName, $rrsets, $nameServer);

                    if ($response['success']) {
                        Log::info("DynDNS synced to nameserver {$nameServer->name}: {$record->name}.{$zone->domain}");
                    } else {
                        Log::error("Failed to sync DynDNS to {$nameServer->name}: " . ($response['error'] ?? 'Unknown error'));
                    }
                } catch (\Exception $e) {
                    Log::error("Exception syncing to nameserver {$nameServer->name}: " . $e->getMessage());
                }
            }
        } catch (\Exception $e) {
            Log::error("Failed to sync record to nameservers: " . $e->getMessage());
        }
    }
}
