<?php

declare(strict_types=1);

namespace App\Domain\Reseller\Services;

use App\Domain\Audit\Services\AuditWriter;
use App\Domain\Dns\Models\DnsZone;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use App\Domain\Sysapi\Contracts\SysapiResult;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

/**
 * Bayi white-label marka yönetimi.
 *
 * Branding alanları users tablosunda (reseller_* prefix) tutulur.
 *
 * Phase 3'te applyWhiteLabel SysAPI üzerinden:
 *   1. Apache vhost yazar (bayinin panel.acme.com domain'i için)
 *   2. Let's Encrypt cert ister (http-01 challenge)
 *   3. Eğer hostname zone'umuzda ise CNAME upsert eder
 */
class ResellerBrandingService
{
    /** Branding cache TTL (saniye) — her istekte DB'ye gitmeyi önler. */
    public const CACHE_TTL = 300;

    public function __construct(
        private readonly SysapiBridge $sysapi,
    ) {}

    /**
     * Bayinin marka bilgilerini döndürür (5dk cache'li).
     *
     * @return array{
     *     brand_name: string|null,
     *     logo_url: string|null,
     *     primary_color: string|null,
     *     support_email: string|null,
     *     support_phone: string|null,
     *     white_label_hostname: string|null,
     * }
     */
    public function getBranding(User $reseller): array
    {
        return Cache::remember(
            self::cacheKey($reseller),
            self::CACHE_TTL,
            fn (): array => [
                'brand_name' => $reseller->reseller_brand_name,
                'logo_url' => $reseller->reseller_logo_url,
                'primary_color' => $reseller->reseller_primary_color ?? '#3b82f6',
                'support_email' => $reseller->reseller_support_email,
                'support_phone' => $reseller->reseller_support_phone,
                'white_label_hostname' => $reseller->reseller_white_label_hostname,
            ],
        );
    }

    /**
     * Branding bilgilerini günceller.
     *
     * @param  array{brand_name?:string, logo_url?:string, primary_color?:string, support_email?:string, support_phone?:string}  $data
     */
    public function updateBranding(User $reseller, array $data): void
    {
        $reseller->update([
            'reseller_brand_name' => $data['brand_name'] ?? $reseller->reseller_brand_name,
            'reseller_logo_url' => $data['logo_url'] ?? $reseller->reseller_logo_url,
            'reseller_primary_color' => $data['primary_color'] ?? $reseller->reseller_primary_color,
            'reseller_support_email' => $data['support_email'] ?? $reseller->reseller_support_email,
            'reseller_support_phone' => $data['support_phone'] ?? $reseller->reseller_support_phone,
        ]);

        $this->invalidateCache($reseller);
    }

    /**
     * White-label hostname kaydet + SysAPI ile vhost+SSL+DNS uygula.
     *
     * Stub davranışı (geriye uyumluluk): $hostname verilirse DB'ye yazılır,
     * sonra applyVhostStack() çağrılır. Bu sayede mevcut controller iki adımı
     * tek call'da yapabilir.
     *
     * @return array{status: string, message: string, hostname: string}
     */
    public function applyWhiteLabel(User $reseller, string $hostname): array
    {
        $hostname = strtolower(trim($hostname));

        $reseller->update(['reseller_white_label_hostname' => $hostname]);
        $this->invalidateCache($reseller);

        // SysAPI üzerinden vhost+cert+dns kur (best effort — failure log'lanır,
        // DB kaydı geri sarılmaz, admin "Apply White-Label" butonuna basabilir).
        try {
            $stack = $this->applyVhostStack($reseller);

            return [
                'status' => $stack['applied'] ? 'applied' : 'pending_dns',
                'message' => $stack['applied']
                    ? "{$hostname} için vhost ve SSL hazır."
                    : "{$hostname} için DNS kaydı oluşturun (A → sunucu IP). SSL otomatik sağlanacak.",
                'hostname' => $hostname,
            ];
        } catch (\Throwable $e) {
            Log::warning('[ResellerBranding] applyVhostStack failed', [
                'reseller_id' => $reseller->id,
                'hostname' => $hostname,
                'error' => $e->getMessage(),
            ]);

            return [
                'status' => 'pending_dns',
                'message' => "{$hostname} kaydedildi. DNS hazır olduğunda 'White-Label Uygula' butonu ile vhost+SSL kurulacak.",
                'hostname' => $hostname,
            ];
        }
    }

    /**
     * White-label hostname'i kaldırır — DB + vhost teardown.
     */
    public function removeWhiteLabel(User $reseller): array
    {
        $hostname = $reseller->reseller_white_label_hostname;

        $reseller->update(['reseller_white_label_hostname' => null]);
        $this->invalidateCache($reseller);

        $vhost = null;
        $dns = null;

        if ($hostname) {
            try {
                $result = $this->sysapi->run('reseller-vhost-remove', [
                    'reseller_id' => $reseller->id,
                    'domain' => $hostname,
                ]);
                $vhost = $result->data;

                if ($this->shouldManageDns($hostname)) {
                    $dnsResult = $this->sysapi->run('record-remove', [
                        'zone' => $this->extractZone($hostname),
                        'name' => $hostname,
                        'type' => 'CNAME',
                    ]);
                    $dns = $dnsResult->data;
                }

                AuditWriter::write(
                    action: 'reseller.whitelabel.removed',
                    target: $reseller,
                    metadata: [
                        'custom_domain' => $hostname,
                        'vhost_path' => $vhost['vhost_path'] ?? null,
                    ],
                );
            } catch (\Throwable $e) {
                Log::warning('[ResellerBranding] removeWhiteLabel sysapi failed', [
                    'reseller_id' => $reseller->id,
                    'hostname' => $hostname,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return [
            'removed' => true,
            'hostname' => $hostname,
            'vhost' => $vhost,
            'dns' => $dns,
        ];
    }

    /**
     * SysAPI ile vhost + cert + (opsiyonel) DNS upsert işlemini gerçekleştirir.
     *
     * Bu metod admin'in "White-Label Uygula" butonunun çağırdığı düşük seviyeli
     * implement noktasıdır. Bayide hostname yoksa atlanır.
     *
     * @return array{
     *     applied: bool,
     *     custom_domain?: string,
     *     skipped?: true,
     *     reason?: string,
     *     vhost?: array<string, mixed>|null,
     *     cert?: array<string, mixed>|null,
     *     dns?: array<string, mixed>|null,
     * }
     */
    public function applyVhostStack(User $reseller): array
    {
        $branding = $this->getBranding($reseller);
        $customDomain = $branding['white_label_hostname'] ?? null;

        if (! $customDomain) {
            return ['applied' => false, 'skipped' => true, 'reason' => 'no_custom_domain'];
        }

        // 1. Apache vhost — http-only ilk pass (cert ondan sonra issue edilebilsin).
        $vhostResult = $this->sysapi->run('reseller-vhost-add', [
            'reseller_id' => $reseller->id,
            'domain' => $customDomain,
            'panel_root' => base_path('public'),
            'ssl_enabled' => false,
        ]);

        // 2. SSL — Let's Encrypt http-01
        $certResult = $this->runSafely('cert-issue', [
            'domain' => $customDomain,
            'method' => 'http-01',
            'webroot' => base_path('public'),
        ]);

        // 3. SSL kurulduysa vhost'u SSL etkin halde yeniden yaz.
        if ($certResult?->ok() && ! empty($certResult->data['cert_path'])) {
            $this->runSafely('reseller-vhost-add', [
                'reseller_id' => $reseller->id,
                'domain' => $customDomain,
                'panel_root' => base_path('public'),
                'ssl_enabled' => true,
                'cert_path' => $certResult->data['cert_path'],
                'key_path' => $certResult->data['key_path'] ?? str_replace('fullchain.pem', 'privkey.pem', (string) $certResult->data['cert_path']),
            ]);
        }

        // 4. DNS — eğer hostname zone'umuzda ise CNAME upsert
        $dnsResult = null;
        if ($this->shouldManageDns($customDomain)) {
            $dnsResult = $this->runSafely('record-upsert', [
                'zone' => $this->extractZone($customDomain),
                'name' => $customDomain,
                'type' => 'CNAME',
                'content' => parse_url((string) config('app.url'), PHP_URL_HOST) ?: 'panel.local',
                'ttl' => 300,
            ]);
        }

        $this->invalidateCache($reseller);

        AuditWriter::write(
            action: 'reseller.whitelabel.applied',
            target: $reseller,
            metadata: [
                'custom_domain' => $customDomain,
                'vhost_path' => $vhostResult->data['vhost_path'] ?? null,
                'cert_path' => $certResult?->data['cert_path'] ?? null,
                'dns_managed' => $dnsResult !== null,
            ],
        );

        Log::info('[ResellerBranding] White-label stack applied', [
            'reseller_id' => $reseller->id,
            'custom_domain' => $customDomain,
        ]);

        return [
            'applied' => true,
            'custom_domain' => $customDomain,
            'vhost' => $vhostResult->data,
            'cert' => $certResult?->data,
            'dns' => $dnsResult?->data,
        ];
    }

    /**
     * Logo dosyasını depolama alanına yükler ve URL'yi döndürür.
     *
     * Storage:link gerekir — `php artisan storage:link` ile public/storage
     * symlink'i kurulmuş olmalı. Aksi takdirde URL erişilemez.
     */
    public function uploadLogo(User $reseller, UploadedFile $file): string
    {
        $path = $file->store("reseller-logos/{$reseller->id}", 'public');
        $url = Storage::disk('public')->url($path);

        $reseller->update(['reseller_logo_url' => $url]);
        $this->invalidateCache($reseller);

        return $url;
    }

    /**
     * Branding cache'ini geçersiz kıl — branding değişimi sonrası çağrılır.
     */
    public function invalidateCache(User $reseller): void
    {
        Cache::forget(self::cacheKey($reseller));
    }

    /**
     * Hostname için bir DnsZone bizde tanımlı mı (PowerDNS yönetimimizde mi)?
     */
    private function shouldManageDns(string $domain): bool
    {
        return DnsZone::query()->where('name', $this->extractZone($domain))->exists();
    }

    /**
     * panel.acme.com → acme.com (apex zone).
     */
    private function extractZone(string $domain): string
    {
        $parts = explode('.', $domain);
        if (count($parts) <= 2) {
            return $domain;
        }

        return implode('.', array_slice($parts, -2));
    }

    /**
     * SysAPI run wrapper — exception'u yutar, log'lar, null döner.
     *
     * @param  array<string, mixed>  $args
     */
    private function runSafely(string $command, array $args): ?SysapiResult
    {
        try {
            return $this->sysapi->run($command, $args);
        } catch (\Throwable $e) {
            Log::warning("[ResellerBranding] sysapi {$command} failed", [
                'args' => $args,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    private static function cacheKey(User $reseller): string
    {
        return "reseller_branding:{$reseller->id}";
    }

    /**
     * White-label hostname için canlı durum kontrolü:
     *   - DNS A record paneli IP'sine işaret ediyor mu?
     *   - HTTPS bağlantı kuruluyor mu?
     *   - Apache vhost dosyası var mı? (sysapi `reseller-vhost-status`)
     *   - SSL cert kaç gün kaldı?
     *
     * UI bunu "Durum" panelinde gösterir. Sonuçlar 60 saniye cache'lenir.
     *
     * @return array{
     *     hostname: string|null,
     *     dns: array{ok: bool, expected: string, actual: string|null, message: string},
     *     vhost: array{ok: bool, message: string, vhost_path?: string},
     *     ssl: array{ok: bool, expires_in_days: int|null, message: string},
     *     overall: 'ok'|'partial'|'failed'|'no_hostname',
     * }
     */
    public function whiteLabelStatus(User $reseller): array
    {
        $hostname = $reseller->reseller_white_label_hostname;

        if (! $hostname) {
            return [
                'hostname' => null,
                'dns' => ['ok' => false, 'expected' => '', 'actual' => null, 'message' => 'White-label aktif değil'],
                'vhost' => ['ok' => false, 'message' => 'Hostname tanımlı değil'],
                'ssl' => ['ok' => false, 'expires_in_days' => null, 'message' => '—'],
                'overall' => 'no_hostname',
            ];
        }

        $cacheKey = "reseller_whitelabel_status:{$reseller->id}";

        return Cache::remember($cacheKey, 60, function () use ($reseller, $hostname): array {
            // Panel IP — config'den veya server_settings'ten al
            $expectedIp = (string) (config('onoxsoft.panel.primary_ip')
                ?? (\Illuminate\Support\Facades\Schema::hasTable('server_settings')
                    ? \Illuminate\Support\Facades\DB::table('server_settings')->where('key', 'primary_ip')->value('value')
                    : null)
                ?? gethostbyname(gethostname()));

            // 1. DNS A lookup
            $dnsResult = $this->checkDns($hostname, $expectedIp);

            // 2. Vhost dosyası kontrolü (sysapi)
            $vhostResult = $this->checkVhost($reseller, $hostname);

            // 3. SSL cert kontrolü
            $sslResult = $this->checkSsl($hostname);

            // Overall durum
            $overall = ($dnsResult['ok'] && $vhostResult['ok'] && $sslResult['ok'])
                ? 'ok'
                : (($dnsResult['ok'] || $vhostResult['ok'])
                    ? 'partial'
                    : 'failed');

            return [
                'hostname' => $hostname,
                'dns' => $dnsResult,
                'vhost' => $vhostResult,
                'ssl' => $sslResult,
                'overall' => $overall,
            ];
        });
    }

    public function invalidateStatusCache(User $reseller): void
    {
        Cache::forget("reseller_whitelabel_status:{$reseller->id}");
    }

    /**
     * @return array{ok: bool, expected: string, actual: string|null, message: string}
     */
    private function checkDns(string $hostname, string $expectedIp): array
    {
        $actual = null;
        try {
            $records = @dns_get_record($hostname, DNS_A);
            if (is_array($records) && ! empty($records)) {
                $actual = $records[0]['ip'] ?? null;
            }
        } catch (\Throwable) {
            // ignore
        }

        if ($actual === null) {
            return [
                'ok' => false,
                'expected' => $expectedIp,
                'actual' => null,
                'message' => "{$hostname} için DNS A kaydı bulunamadı. Registrar panelinizde A kaydı oluşturun: {$expectedIp}",
            ];
        }

        $ok = $actual === $expectedIp;
        return [
            'ok' => $ok,
            'expected' => $expectedIp,
            'actual' => $actual,
            'message' => $ok
                ? "DNS doğru ({$actual})"
                : "DNS yanlış IP'ye işaret ediyor: gerçek={$actual}, beklenen={$expectedIp}",
        ];
    }

    /**
     * @return array{ok: bool, message: string, vhost_path?: string}
     */
    private function checkVhost(User $reseller, string $hostname): array
    {
        try {
            $result = $this->sysapi->run('reseller-vhost-status', [
                'reseller_id' => $reseller->id,
                'domain' => $hostname,
            ], 10);

            if (! $result->ok()) {
                return ['ok' => false, 'message' => 'Vhost dosyası bulunamadı'];
            }

            $exists = (bool) ($result->data['exists'] ?? false);
            $vhostPath = (string) ($result->data['vhost_path'] ?? '');

            return [
                'ok' => $exists,
                'message' => $exists ? "Vhost aktif: {$vhostPath}" : 'Vhost dosyası yok — "Yeniden Uygula" butonu ile kurun',
                'vhost_path' => $vhostPath,
            ];
        } catch (\Throwable $e) {
            return [
                'ok' => false,
                'message' => 'Vhost durumu kontrol edilemedi: '.$e->getMessage(),
            ];
        }
    }

    /**
     * @return array{ok: bool, expires_in_days: int|null, message: string}
     */
    private function checkSsl(string $hostname): array
    {
        // openssl s_client ile cert info çek (timeout 5sn)
        try {
            $ctx = stream_context_create([
                'ssl' => [
                    'capture_peer_cert' => true,
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                    'SNI_enabled' => true,
                    'peer_name' => $hostname,
                ],
            ]);

            $errno = 0;
            $errstr = '';
            $sock = @stream_socket_client(
                "ssl://{$hostname}:443",
                $errno,
                $errstr,
                5,
                STREAM_CLIENT_CONNECT,
                $ctx
            );

            if (! $sock) {
                return [
                    'ok' => false,
                    'expires_in_days' => null,
                    'message' => "HTTPS bağlantısı kurulamadı ({$errstr})",
                ];
            }

            $params = stream_context_get_params($sock);
            fclose($sock);

            $cert = $params['options']['ssl']['peer_certificate'] ?? null;
            if (! $cert) {
                return ['ok' => false, 'expires_in_days' => null, 'message' => 'Cert okunamadı'];
            }

            $info = openssl_x509_parse($cert);
            if (! $info || empty($info['validTo_time_t'])) {
                return ['ok' => false, 'expires_in_days' => null, 'message' => 'Cert metadata parse hatası'];
            }

            $daysLeft = (int) floor(($info['validTo_time_t'] - time()) / 86400);
            $issuer = $info['issuer']['CN'] ?? 'Unknown';

            return [
                'ok' => $daysLeft > 0,
                'expires_in_days' => $daysLeft,
                'message' => $daysLeft > 0
                    ? "SSL geçerli: {$issuer} ({$daysLeft} gün kaldı)"
                    : "SSL süresi dolmuş!",
            ];
        } catch (\Throwable $e) {
            return [
                'ok' => false,
                'expires_in_days' => null,
                'message' => 'SSL kontrolü başarısız: '.$e->getMessage(),
            ];
        }
    }
}
