<?php
class LeaseService {
    /**
     * Récupère tous les leases IPv4 via l'API Kea Control Agent.
     * Fonctionne en mode AlmaLinux ET FreeBSD (appel direct à kea-ctrl-agent).
     * Retourne ['success' => bool, 'leases' => array, 'message' => string].
     */
    public static function getLeases(): array {
        $url = rtrim(Settings::get('kea_api_url', 'http://localhost:8000'), '/') . '/';
        if ($url === '/') {
            return ['success' => false, 'leases' => [], 'message' => 'URL Kea Control Agent non configurée.'];
        }

        // Certaines versions de Kea exigent la liste des subnets dans les arguments.
        // On passe tous les kea_subnet_id gérés par l'application.
        $subnetIds = array_map('intval', Database::get()->query(
            'SELECT kea_subnet_id FROM app_subnets ORDER BY kea_subnet_id'
        )->fetchAll(PDO::FETCH_COLUMN));

        $payload = json_encode([
            'command'   => 'lease4-get-all',
            'service'   => ['dhcp4'],
            'arguments' => ['subnets' => $subnetIds],
        ]);
        $headers = ['Content-Type: application/json'];

        $apiUser = Settings::get('kea_api_user', '');
        if ($apiUser !== '') {
            $token     = base64_encode($apiUser . ':' . Settings::get('kea_api_password', ''));
            $headers[] = 'Authorization: Basic ' . $token;
        }

        $result = self::httpPost($url, $payload, $headers);
        if ($result === false) {
            return ['success' => false, 'leases' => [], 'message' => 'Impossible de joindre l\'API Kea : ' . $url];
        }

        $data = json_decode($result, true);
        if (!is_array($data) || !isset($data[0])) {
            return ['success' => false, 'leases' => [], 'message' => 'Réponse invalide de l\'API Kea.'];
        }

        $res = $data[0];
        // result 0 = succès, result 3 = vide (aucun lease trouvé)
        if (!in_array($res['result'] ?? -1, [0, 3], true)) {
            return ['success' => false, 'leases' => [], 'message' => $res['text'] ?? 'Erreur inconnue Kea API'];
        }

        $leases = $res['arguments']['leases'] ?? [];
        return ['success' => true, 'leases' => $leases, 'message' => count($leases) . ' lease(s) trouvé(s)'];
    }

    /**
     * Leases enrichis pour l'affichage (page Leases + endpoint AJAX leases_data.php).
     * Croise les leases Kea avec les subnets applicatifs et les réservations existantes.
     * Retour ['success'=>bool, 'leases'=>array, 'message'=>string] ; chaque lease enrichi :
     *   ip, ip_int, mac, hostname, subnet_id, subnet_name, subnet_app_id, state, expiry,
     *   host_id (null si pas de réservation), group_name.
     */
    public static function getEnrichedLeases(): array {
        $apiResult = static::getLeases();

        // Map subnets par kea_subnet_id
        $subnetMap = [];
        foreach (Subnet::getAll() as $s) {
            $subnetMap[(int)$s['kea_subnet_id']] = $s;
        }

        // Map réservations : "machex:subnet_id" → [host_id, group_name]
        $resRows = Database::get()->query(
            'SELECT h.host_id, HEX(h.dhcp_identifier) AS mac_hex, h.dhcp4_subnet_id,
                    m.group_name
             FROM hosts h
             LEFT JOIN app_reservation_meta m ON m.host_id = h.host_id
             WHERE h.dhcp_identifier_type = ' . Reservation::HW_ADDR_TYPE
        )->fetchAll();
        $resMap = [];
        foreach ($resRows as $r) {
            $key = strtolower($r['mac_hex']) . ':' . $r['dhcp4_subnet_id'];
            $resMap[$key] = ['host_id' => (int)$r['host_id'], 'group_name' => $r['group_name'] ?? ''];
        }

        $leasesData = [];
        if ($apiResult['success']) {
            foreach ($apiResult['leases'] as $lease) {
                $mac      = $lease['hw-address'] ?? '';
                $ip       = $lease['ip-address'] ?? '';
                $subnetId = (int)($lease['subnet-id'] ?? 0);
                $cltt     = (int)($lease['cltt']      ?? 0);
                $validLft = (int)($lease['valid-lft'] ?? 0);
                $macHex   = strtolower(str_replace(':', '', $mac));
                $resKey   = $macHex . ':' . $subnetId;
                $sub      = $subnetMap[$subnetId] ?? null;
                $res      = $resMap[$resKey] ?? null;

                $leasesData[] = [
                    'ip'            => $ip,
                    'ip_int'        => sprintf('%u', ip2long($ip) ?: 0),
                    'mac'           => $mac,
                    'hostname'      => $lease['hostname'] ?? '',
                    'subnet_id'     => $subnetId,
                    'subnet_name'   => $sub ? $sub['name'] : ('Subnet #' . $subnetId),
                    'subnet_app_id' => $sub ? (int)$sub['id'] : null,
                    'state'         => (int)($lease['state'] ?? 0),
                    'expiry'        => $cltt + $validLft,
                    'host_id'       => $res ? $res['host_id'] : null,
                    'group_name'    => $res ? $res['group_name'] : '',
                ];
            }
        }

        return [
            'success' => $apiResult['success'],
            'leases'  => $leasesData,
            'message' => $apiResult['message'],
        ];
    }

    /**
     * Libère (supprime) un lease IPv4 via l'API Kea (lease4-del).
     * Retour ['success'=>bool, 'existed'=>bool, 'message'=>string] :
     *   result 0 → supprimé ; result 3 → déjà absent (pas une erreur) ; autre → échec.
     */
    public static function releaseLease(string $ip): array {
        $url = rtrim(Settings::get('kea_api_url', 'http://localhost:8000'), '/') . '/';
        if ($url === '/') {
            return ['success' => false, 'existed' => false, 'message' => 'URL Kea Control Agent non configurée.'];
        }
        $payload = json_encode([
            'command'   => 'lease4-del',
            'service'   => ['dhcp4'],
            'arguments' => ['ip-address' => $ip],
        ]);
        $headers = ['Content-Type: application/json'];
        $apiUser = Settings::get('kea_api_user', '');
        if ($apiUser !== '') {
            $headers[] = 'Authorization: Basic ' . base64_encode($apiUser . ':' . Settings::get('kea_api_password', ''));
        }

        $result = static::httpPost($url, $payload, $headers);
        if ($result === false) {
            return ['success' => false, 'existed' => false, 'message' => 'Impossible de joindre l\'API Kea : ' . $url];
        }
        $data = json_decode($result, true);
        if (!is_array($data) || !isset($data[0])) {
            return ['success' => false, 'existed' => false, 'message' => 'Réponse invalide de l\'API Kea.'];
        }
        $res = $data[0]['result'] ?? -1;
        if ($res === 0) {
            return ['success' => true, 'existed' => true, 'message' => 'Lease libéré.'];
        }
        if ($res === 3) {
            return ['success' => true, 'existed' => false, 'message' => 'Lease déjà absent (expiré).'];
        }
        return ['success' => false, 'existed' => false, 'message' => $data[0]['text'] ?? 'Erreur inconnue Kea API'];
    }

    /**
     * Parmi les IP données, retourne les leases ACTIFS (state=0) trouvés.
     * Un seul appel getLeases(), filtrage côté PHP.
     * Retour : [ ip => ['mac'=>string, 'hostname'=>string, 'expiry'=>int], ... ]
     * Si l'API est injoignable, retourne [] (best-effort — ne bloque jamais l'appelant).
     */
    public static function findActiveLeases(array $ips): array {
        $wanted = [];
        foreach ($ips as $ip) {
            $ip = trim((string)$ip);
            if ($ip !== '') { $wanted[$ip] = true; }
        }
        if (!$wanted) { return []; }

        $all = static::getLeases();
        if (empty($all['success'])) { return []; }

        $out = [];
        foreach ($all['leases'] as $l) {
            $ip = $l['ip-address'] ?? '';
            if (!isset($wanted[$ip])) { continue; }
            if ((int)($l['state'] ?? 0) !== 0) { continue; } // actifs uniquement
            $out[$ip] = [
                'mac'      => $l['hw-address'] ?? '',
                'hostname' => $l['hostname']   ?? '',
                'expiry'   => (int)($l['cltt'] ?? 0) + (int)($l['valid-lft'] ?? 0),
            ];
        }
        return $out;
    }

    /**
     * Leases actifs « orphelins » après écriture d'une réservation MAC_new / IP_new.
     * $ipOld : IP actuelle de la résa (édition) ou null (création).
     * Règles :
     *   - IP_new : lease actif tenu par une MAC ≠ MAC_new (IP cible occupée par un autre).
     *   - IP_old (si ≠ IP_new et non vide) : tout lease actif (bail fantôme).
     * Retour : liste [ ['ip'=>string,'mac'=>string,'hostname'=>string,'expiry'=>int], ... ].
     */
    public static function orphanLeasesForReservation(string $macNew, string $ipNew, ?string $ipOld = null): array {
        $ipNew = trim($ipNew);
        $ipOld = $ipOld !== null ? trim($ipOld) : '';
        $macNew = strtolower(trim($macNew));

        $toCheck = [];
        if ($ipNew !== '') { $toCheck[] = $ipNew; }
        if ($ipOld !== '' && $ipOld !== $ipNew) { $toCheck[] = $ipOld; }
        if (!$toCheck) { return []; }

        $active = static::findActiveLeases($toCheck);
        $out = [];
        foreach ($active as $ip => $info) {
            if ($ip === $ipNew) {
                // Orphelin seulement si tenu par une autre MAC
                if (strtolower($info['mac']) === $macNew) { continue; }
            }
            // Pour IP_old : tout lease actif est orphelin (déjà filtré actif par findActiveLeases)
            $out[] = ['ip' => $ip, 'mac' => $info['mac'], 'hostname' => $info['hostname'], 'expiry' => $info['expiry']];
        }
        return $out;
    }

    protected static function httpPost(string $url, string $body, array $headers = []): string|false {
        if (!function_exists('curl_init')) {
            $opts = [
                'http' => [
                    'method'         => 'POST',
                    'header'         => implode("\r\n", $headers),
                    'content'        => $body,
                    'timeout'        => 10,
                    'ignore_errors'  => true,
                ],
            ];
            return @file_get_contents($url, false, stream_context_create($opts));
        }

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_CONNECTTIMEOUT => 5,
        ]);
        $response = curl_exec($ch);
        $err      = curl_errno($ch);
        // curl_close() supprimé : no-op depuis PHP 8.0 (CurlHandle libéré par le GC), déprécié en 8.5
        return $err === 0 ? $response : false;
    }
}
