<?php

namespace App\Modules\Dns\Cloudflare;

use App\Core\Dns\AbstractDnsDriver;
use Illuminate\Support\Facades\Http;
use RuntimeException;

/**
 * PRC-95 — Cloudflare DNS driver.
 *
 * Auth: Bearer API token (recommended over Global API Key).
 * Endpoints:
 *   POST /zones                                  → create zone
 *   GET  /zones?name={domain}                    → lookup
 *   DELETE /zones/{id}
 *   GET  /zones/{id}/dns_records
 *   POST /zones/{id}/dns_records
 *   PUT/DELETE /zones/{id}/dns_records/{rid}
 */
class Cloudflare extends AbstractDnsDriver
{
    private const BASE_URL = 'https://api.cloudflare.com/client/v4';

    private const TIMEOUT = 30;

    public function getName(): string
    {
        return 'cloudflare';
    }

    public function getLabel(): string
    {
        return 'Cloudflare';
    }

    public function adminFields(): array
    {
        return [
            'api_token' => [
                'type' => 'password',
                'label' => 'Cloudflare API Token',
                'required' => true,
                'description' => 'Create token at dash.cloudflare.com/profile/api-tokens with Zone:DNS:Edit permission.',
            ],
            'account_id' => [
                'type' => 'text',
                'label' => 'Account ID',
                'required' => true,
                'description' => 'Found on the Cloudflare dashboard right sidebar (any domain → API section).',
            ],
        ];
    }

    public function createZone(string $domain): string
    {
        $resp = $this->call('POST', '/zones', [
            'name' => $domain,
            'account' => ['id' => $this->getConfig('account_id')],
            'type' => 'full',
        ]);

        $zoneId = $resp['result']['id'] ?? null;
        if (! $zoneId) {
            throw new RuntimeException('Cloudflare createZone returned no zone id');
        }

        return (string) $zoneId;
    }

    public function deleteZone(string $zoneId): void
    {
        try {
            $this->call('DELETE', "/zones/{$zoneId}");
        } catch (\Throwable $e) {
            // 404 = already gone — idempotent
            if (! str_contains($e->getMessage(), 'HTTP 404')) {
                throw $e;
            }
        }
    }

    public function listRecords(string $zoneId): array
    {
        $resp = $this->call('GET', "/zones/{$zoneId}/dns_records");
        $out = [];
        foreach (($resp['result'] ?? []) as $r) {
            $out[] = [
                'id' => (string) $r['id'],
                'type' => (string) $r['type'],
                'name' => (string) $r['name'],
                'content' => (string) $r['content'],
                'ttl' => (int) ($r['ttl'] ?? 0),
                'priority' => isset($r['priority']) ? (int) $r['priority'] : null,
            ];
        }

        return $out;
    }

    public function createRecord(string $zoneId, array $record): string
    {
        $body = [
            'type' => $record['type'],
            'name' => $record['name'],
            'content' => $record['content'],
            'ttl' => $record['ttl'] ?? 1,    // 1 = automatic
        ];
        if (isset($record['priority'])) {
            $body['priority'] = $record['priority'];
        }
        if (isset($record['proxied'])) {
            $body['proxied'] = (bool) $record['proxied'];
        }

        $resp = $this->call('POST', "/zones/{$zoneId}/dns_records", $body);
        $recordId = $resp['result']['id'] ?? null;
        if (! $recordId) {
            throw new RuntimeException('Cloudflare createRecord returned no id');
        }

        return (string) $recordId;
    }

    public function updateRecord(string $zoneId, string $recordId, array $record): void
    {
        $body = [
            'type' => $record['type'],
            'name' => $record['name'],
            'content' => $record['content'],
            'ttl' => $record['ttl'] ?? 1,
        ];
        if (isset($record['priority'])) {
            $body['priority'] = $record['priority'];
        }
        if (isset($record['proxied'])) {
            $body['proxied'] = (bool) $record['proxied'];
        }
        $this->call('PUT', "/zones/{$zoneId}/dns_records/{$recordId}", $body);
    }

    public function deleteRecord(string $zoneId, string $recordId): void
    {
        try {
            $this->call('DELETE', "/zones/{$zoneId}/dns_records/{$recordId}");
        } catch (\Throwable $e) {
            if (! str_contains($e->getMessage(), 'HTTP 404')) {
                throw $e;
            }
        }
    }

    public function testConnection(): array
    {
        $token = $this->getConfig('api_token');
        if (! $token) {
            return ['success' => false, 'message' => 'api_token is required'];
        }
        try {
            $resp = Http::withToken($token)
                ->acceptJson()
                ->timeout(15)
                ->get(self::BASE_URL.'/user/tokens/verify');

            if (! $resp->successful()) {
                return ['success' => false, 'message' => 'Cloudflare HTTP '.$resp->status()];
            }
            $body = $resp->json();
            $status = $body['result']['status'] ?? 'unknown';
            $isValid = ($body['success'] ?? false) === true && $status === 'active';

            return $isValid
                ? ['success' => true, 'message' => 'Cloudflare token verified (active)']
                : ['success' => false, 'message' => "Token status: {$status}"];
        } catch (\Throwable $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    /**
     * @return array decoded JSON body
     */
    protected function call(string $method, string $path, array $body = []): array
    {
        $token = $this->getConfig('api_token');
        if (! $token) {
            throw new RuntimeException('Cloudflare api_token missing');
        }

        $req = Http::withToken($token)
            ->acceptJson()
            ->timeout(self::TIMEOUT);

        $url = self::BASE_URL.$path;
        $resp = match ($method) {
            'GET' => $req->get($url, $body),
            'POST' => $req->post($url, $body),
            'PUT' => $req->put($url, $body),
            'DELETE' => $req->delete($url),
        };

        if (! $resp->successful()) {
            $errMsg = 'HTTP '.$resp->status();
            $j = $resp->json();
            if (is_array($j) && ! empty($j['errors'])) {
                $errMsg .= ': '.json_encode($j['errors']);
            }
            throw new RuntimeException("Cloudflare {$errMsg}");
        }

        return $resp->json() ?? [];
    }
}
