<?php

declare(strict_types=1);

namespace App\Domain\Dns\Services;

use App\Domain\Account\Models\Account;
use App\Domain\Dns\Models\DnsRecord;
use App\Domain\Dns\Models\DnsTemplate;
use App\Domain\Dns\Models\DnsZone;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

/**
 * DnsTemplateApplier — DB-driven DNS şablonu çözümleyici + uygulayıcı.
 *
 * resolveTemplate() çağrı sırası:
 *   1. Explicit template_id (Controller'dan)
 *   2. Account'un paketine atanmış template
 *   3. is_default=true global template
 *   4. null → caller hardcoded DnsRecordTemplate fallback'a düşer
 *
 * apply() her record için placeholder substitution yapar ve DnsRecord tablosuna yazar.
 * SOA + NS kayıtları DnsProvisioner::createZone içinde ayrı yazılır, burada YOK.
 */
final class DnsTemplateApplier
{
    /**
     * Account + opsiyonel explicit template_id'ye göre uygun template'i çöz.
     *
     * Defensive: dns_templates tablosu yoksa null döner — caller fallback'a düşer.
     */
    public function resolveTemplate(Account $account, ?int $explicitTemplateId = null): ?DnsTemplate
    {
        if (! Schema::hasTable('dns_templates')) {
            return null;
        }

        // 1) Explicit override (Controller'dan template_id geldiyse)
        if ($explicitTemplateId !== null) {
            $tpl = DnsTemplate::where('id', $explicitTemplateId)
                ->where('is_active', true)
                ->first();
            if ($tpl) {
                return $tpl;
            }
        }

        // 2) Paket-spesifik template
        if ($account->package_id ?? null) {
            $tpl = DnsTemplate::where('applies_to_package_id', $account->package_id)
                ->where('is_active', true)
                ->first();
            if ($tpl) {
                return $tpl;
            }
        }

        // 3) Global default
        return DnsTemplate::query()
            ->whereNull('applies_to_package_id')
            ->where('is_active', true)
            ->where('is_default', true)
            ->first();
    }

    /**
     * Template'i bir zone'a uygula — placeholder substitution + DB insert.
     *
     * @param  array{server_ip?: string, ttl?: int, mail_host?: string}  $context
     * @return int  Eklenen kayıt sayısı
     */
    public function apply(DnsZone $zone, DnsTemplate $template, array $context = []): int
    {
        $domain = $zone->name;
        $placeholders = $this->buildPlaceholderMap($template, $context, $domain);

        return DB::transaction(function () use ($zone, $template, $placeholders) {
            $count = 0;
            foreach ($template->records as $tplRec) {
                $name = $this->substitute($tplRec->name, $placeholders);

                // '@' apex shortcut → tam zone adı
                if ($name === '@' || $name === '') {
                    $name = $zone->name;
                }

                DnsRecord::create([
                    'zone_id' => $zone->id,
                    'name' => $name,
                    'type' => $tplRec->type,
                    'content' => $this->substitute($tplRec->content, $placeholders),
                    'ttl' => $tplRec->ttl,
                    'prio' => $tplRec->prio,
                    'disabled' => false,
                    'auth' => true,
                ]);
                $count++;
            }

            $zone->increment('notified_serial');

            return $count;
        });
    }

    /**
     * Bir template'i belirli bir domain için ön-izle — DB'ye yazmadan.
     * Controller `preview` endpoint'i için.
     *
     * @return array<int, array{name: string, type: string, content: string, ttl: int, prio: int|null}>
     */
    public function preview(DnsTemplate $template, string $domain, array $context = []): array
    {
        $placeholders = $this->buildPlaceholderMap($template, $context, $domain);

        $out = [];
        foreach ($template->records as $tplRec) {
            $name = $this->substitute($tplRec->name, $placeholders);
            if ($name === '@' || $name === '') {
                $name = $domain;
            }
            $out[] = [
                'name' => $name,
                'type' => $tplRec->type,
                'content' => $this->substitute($tplRec->content, $placeholders),
                'ttl' => $tplRec->ttl,
                'prio' => $tplRec->prio,
            ];
        }

        return $out;
    }

    /**
     * Placeholder haritası birleştir — runtime context > template defaults > sane fallbacks.
     */
    private function buildPlaceholderMap(DnsTemplate $template, array $context, string $domain): array
    {
        $defaults = is_array($template->placeholders) ? $template->placeholders : [];

        $serverIp = $context['server_ip']
            ?? $defaults['server_ip']
            ?? $this->detectServerIp();

        $mailHost = $context['mail_host']
            ?? $defaults['mail_host']
            ?? "mail.{$domain}";

        $map = [
            'DOMAIN' => $domain,
            'SERVER_IP' => $serverIp,
            'MAIL_HOST' => $mailHost,
        ];

        // Template'in placeholders JSON'ından gelen ekstra kullanıcı tanımlı keyler
        foreach ($defaults as $k => $v) {
            if (! is_string($k) || ! is_scalar($v)) {
                continue;
            }
            $upper = strtoupper($k);
            if (! isset($map[$upper])) {
                $map[$upper] = (string) $v;
            }
        }

        // Runtime context override (en yüksek öncelik)
        foreach ($context as $k => $v) {
            if (! is_string($k) || ! is_scalar($v)) {
                continue;
            }
            $map[strtoupper($k)] = (string) $v;
        }

        return $map;
    }

    /**
     * ${KEY} formatındaki placeholder'ları substitute et.
     */
    private function substitute(string $template, array $placeholders): string
    {
        return preg_replace_callback('/\$\{([A-Z0-9_]+)\}/', function ($m) use ($placeholders) {
            return $placeholders[$m[1]] ?? $m[0];
        }, $template) ?? $template;
    }

    /**
     * Server IP detection (config + server_settings + SERVER_ADDR fallback).
     */
    private function detectServerIp(): string
    {
        if (Schema::hasTable('server_settings')) {
            $ip = DB::table('server_settings')->where('key', 'primary_ip')->value('value');
            if (is_string($ip) && $ip !== '') {
                return $ip;
            }
        }

        $configured = config('onoxsoft.server_ip');
        if (is_string($configured) && $configured !== '') {
            return $configured;
        }

        $request = function_exists('request') ? request() : null;
        if ($request !== null) {
            $addr = $request->server('SERVER_ADDR');
            if (is_string($addr) && $addr !== '') {
                return $addr;
            }
        }

        return '127.0.0.1';
    }
}
