<?php

namespace App\Service;

/**
 * @codeCoverageIgnore
 */
class DnsInfraHelper
{
    public const DOMAINS = ['e-cosplay.fr', 'esy-web.dev'];

    public const EXPECTED_MX = [
        'e-cosplay.fr' => 'mail.esy-web.dev',
        'esy-web.dev' => 'mail.esy-web.dev',
    ];

    public const LABEL_AWS_SES = 'AWS SES';
    public const LABEL_MAILCOW = 'Mailcow';
    public const LABEL_MAILCOW_DNS = 'Mailcow DNS';
    public const NOT_FOUND = 'Non trouve';
    public const NOT_CONFIGURED = 'Non configure';

    public function __construct(
        private DnsCheckService $dnsCheck,
        private CloudflareService $cloudflare,
    ) {
    }

    /**
     * @return array<string, list<array<string, mixed>>>
     */
    public function loadCloudflareRecords(): array
    {
        $result = [];
        if (!$this->cloudflare->isAvailable()) {
            return $result;
        }

        foreach (self::DOMAINS as $domain) {
            try {
                $zoneId = $this->cloudflare->getZoneId($domain);
                if (null !== $zoneId) {
                    $result[$domain] = $this->cloudflare->getDnsRecords($zoneId);
                }
            } catch (\Throwable) {
            }
        }

        return $result;
    }

    /**
     * @param list<array>                $checks
     * @param list<array<string, mixed>> $cfRecords
     */
    public function enrichWithCloudflare(array &$checks, string $recordName, string $checkType, string $dnsType, array $cfRecords): void
    {
        $cfValue = null;
        $cfStatus = '';

        foreach ($cfRecords as $r) {
            if (($r['name'] ?? '') === $recordName && ($r['type'] ?? '') === $dnsType) {
                $cfValue = $r['content'] ?? '';
                $cfStatus = 'ok';
                break;
            }
        }

        if (null === $cfValue) {
            $cfValue = self::NOT_FOUND;
            $cfStatus = '' === $cfStatus ? '' : 'error';
        }

        for ($i = \count($checks) - 1; $i >= 0; --$i) {
            if ($checks[$i]['type'] === $checkType && '' === ($checks[$i]['cloudflare'] ?? '')) {
                $checks[$i]['cloudflare'] = $cfValue;
                $checks[$i]['cf_status'] = $cfStatus;
            }
        }
    }

    /**
     * @param list<array>                $checks
     * @param list<array<string, mixed>> $cfRecords
     */
    public function enrichLastCheck(array &$checks, string $recordName, string $dnsType, array $cfRecords): void
    {
        $cfValue = self::NOT_FOUND;
        $cfStatus = '';

        foreach ($cfRecords as $r) {
            if (($r['name'] ?? '') === $recordName && ($r['type'] ?? '') === $dnsType) {
                $cfValue = $r['content'] ?? '';
                $cfStatus = 'ok';
                break;
            }
        }

        $last = \count($checks) - 1;
        if ($last >= 0) {
            $checks[$last]['cloudflare'] = $cfValue;
            $checks[$last]['cf_status'] = $cfStatus;
        }
    }

    public function getActualDnsValue(string $type, string $name): string
    {
        return match ($type) {
            'MX' => $this->getMxValues($name),
            'CNAME' => $this->dnsCheck->getCnameRecord($name) ?? '',
            'TXT' => $this->getFirstTxtValue($name),
            'SRV' => $this->getSrvValue($name),
            default => '',
        };
    }

    public function getMxValues(string $domain): string
    {
        $values = [];
        foreach ($this->dnsCheck->getMxRecords($domain) as $mx) {
            $values[] = $mx['target'].' (pri: '.$mx['pri'].')';
        }

        return implode(', ', $values);
    }

    public function getFirstTxtValue(string $domain): string
    {
        $output = $this->dnsCheck->dig($domain, 'TXT');

        foreach (explode("\n", $output) as $line) {
            if (preg_match('/\bIN\s+TXT\s+"(.+)"/', $line, $m)) {
                return str_replace('" "', '', $m[1]);
            }
        }

        return '';
    }

    public function getSrvValue(string $domain): string
    {
        $values = [];
        foreach ($this->dnsCheck->getSrvRecords($domain) as $srv) {
            $values[] = $srv['target'].' port:'.$srv['port'];
        }

        return implode(', ', $values);
    }

    public function checkMxExists(string $name, string $target): bool
    {
        foreach ($this->dnsCheck->getMxRecords($name) as $mx) {
            if (str_contains($mx['target'], $target)) {
                return true;
            }
        }

        return false;
    }

    public function checkTxtContains(string $name, string $start): bool
    {
        $output = $this->dnsCheck->dig($name, 'TXT');

        foreach (explode("\n", $output) as $line) {
            if (preg_match('/\bIN\s+TXT\s+"(.+)"/', $line, $m)) {
                $txt = str_replace('" "', '', $m[1]);
                if (str_starts_with($txt, $start)) {
                    return true;
                }
            }
        }

        return false;
    }

    public function getTxtSpfValue(string $domain): string
    {
        $output = $this->dnsCheck->dig($domain, 'TXT');

        foreach (explode("\n", $output) as $line) {
            if (preg_match('/\bIN\s+TXT\s+"(.+)"/', $line, $m)) {
                $txt = str_replace('" "', '', $m[1]);
                if (str_starts_with($txt, 'v=spf1')) {
                    return $txt;
                }
            }
        }

        return '';
    }

    public function checkDnsRecordExists(string $type, string $name, string $expectedContent): bool
    {
        return match ($type) {
            'MX' => $this->checkMxExists($name, $expectedContent),
            'TXT' => $this->checkTxtContains($name, $expectedContent),
            'CNAME' => null !== $this->dnsCheck->getCnameRecord($name),
            'SRV' => [] !== $this->dnsCheck->getSrvRecords($name),
            default => false,
        };
    }

    public function getDnsCheck(): DnsCheckService
    {
        return $this->dnsCheck;
    }
}
