<?php
class Subnet {
    /** Tous les subnets avec leur nombre de réservations. */
    public static function getAll(): array {
        $db = Database::get();
        $rows = $db->query(
            'SELECT s.*, COUNT(h.host_id) AS reservation_count
             FROM app_subnets s
             LEFT JOIN hosts h ON h.dhcp4_subnet_id = s.kea_subnet_id
                               AND h.dhcp_identifier_type = ' . Reservation::HW_ADDR_TYPE . '
             GROUP BY s.id
             ORDER BY s.kea_subnet_id'
        )->fetchAll();
        return $rows;
    }

    public static function getById(int $id): ?array {
        $stmt = Database::get()->prepare(
            'SELECT s.*, COUNT(h.host_id) AS reservation_count
             FROM app_subnets s
             LEFT JOIN hosts h ON h.dhcp4_subnet_id = s.kea_subnet_id
                               AND h.dhcp_identifier_type = ' . Reservation::HW_ADDR_TYPE . '
             WHERE s.id = ?
             GROUP BY s.id'
        );
        $stmt->execute([$id]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    /** Subnets publics (pour le formulaire de demande) — id et name uniquement. */
    public static function getPublic(): array {
        return Database::get()->query(
            'SELECT id, name FROM app_subnets WHERE is_public = 1 AND is_active = 1 ORDER BY name'
        )->fetchAll();
    }

    public static function getByKeaSubnetId(int $keaId): ?array {
        $stmt = Database::get()->prepare(
            'SELECT * FROM app_subnets WHERE kea_subnet_id = ?'
        );
        $stmt->execute([$keaId]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    public static function add(array $data): int {
        $db = Database::get();
        $db->prepare(
            'INSERT INTO app_subnets
             (kea_subnet_id, name, cidr, interface, gateway, relay_address, dns_servers, ntp_servers,
              range_start, range_end, has_dynamic_pool, is_public, notes,
              short_lease_pool_start, short_lease_pool_end)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
        )->execute([
            (int)$data['kea_subnet_id'],
            trim($data['name']),
            trim($data['cidr']),
            trim($data['interface']      ?? ''),
            trim($data['gateway']        ?? ''),
            trim($data['relay_address']  ?? '') ?: null,
            trim($data['dns_servers']    ?? ''),
            trim($data['ntp_servers']    ?? ''),
            trim($data['range_start']    ?? ''),
            trim($data['range_end']      ?? ''),
            empty($data['has_dynamic_pool']) ? 0 : 1,
            empty($data['is_public'])    ? 0 : 1,
            trim($data['notes'] ?? ''),
            trim($data['short_lease_pool_start'] ?? '') ?: null,
            trim($data['short_lease_pool_end']   ?? '') ?: null,
        ]);
        return (int)$db->lastInsertId();
    }

    public static function update(int $id, array $data): void {
        Database::get()->prepare(
            'UPDATE app_subnets
             SET kea_subnet_id = ?, name = ?, cidr = ?, interface = ?,
                 gateway = ?, relay_address = ?, dns_servers = ?, ntp_servers = ?,
                 range_start = ?, range_end = ?,
                 has_dynamic_pool = ?, is_public = ?, notes = ?,
                 short_lease_pool_start = ?, short_lease_pool_end = ?
             WHERE id = ?'
        )->execute([
            (int)$data['kea_subnet_id'],
            trim($data['name']),
            trim($data['cidr']),
            trim($data['interface']     ?? ''),
            trim($data['gateway']       ?? ''),
            trim($data['relay_address'] ?? '') ?: null,
            trim($data['dns_servers']   ?? ''),
            trim($data['ntp_servers']   ?? ''),
            trim($data['range_start']   ?? ''),
            trim($data['range_end']     ?? ''),
            empty($data['has_dynamic_pool']) ? 0 : 1,
            empty($data['is_public'])    ? 0 : 1,
            trim($data['notes'] ?? ''),
            trim($data['short_lease_pool_start'] ?? '') ?: null,
            trim($data['short_lease_pool_end']   ?? '') ?: null,
            $id,
        ]);
    }

    public static function delete(int $id): void {
        Database::get()->prepare('DELETE FROM app_subnets WHERE id = ?')->execute([$id]);
    }

    public static function toggle(int $id): void {
        Database::get()->prepare(
            'UPDATE app_subnets SET is_active = 1 - is_active WHERE id = ?'
        )->execute([$id]);
    }

    /**
     * Synchronise app_subnets (actifs uniquement) vers les tables natives Kea
     * (dhcp4_subnet, dhcp4_pool, dhcp4_options, dhcp4_subnet_server).
     * Utilisé en mode AlmaLinux avant config-reload.
     */
    public static function syncToKea(): void {
        $db  = Database::get();
        $now = date('Y-m-d H:i:s');

        // createAuditRevisionDHCP4 initialise l'état interne requis par les triggers Kea.
        // Ne pas insérer manuellement dans dhcp4_audit_revision — utiliser la procédure officielle.
        $db->exec("CALL createAuditRevisionDHCP4('$now', 'all', 'DHCPMan sync', 0)");

        // Upsert client class 'short-lease' (valid-lifetime 1h — contournement routes Windows)
        $db->prepare(
            'INSERT INTO dhcp4_client_class (name, valid_lifetime, modification_ts)
             VALUES (\'short-lease\', 3600, ?)
             ON DUPLICATE KEY UPDATE valid_lifetime = 3600, modification_ts = ?'
        )->execute([$now, $now]);
        // Lier au server_id 1 (tag \'all\') si pas déjà présent
        $classId = (int)$db->query(
            "SELECT id FROM dhcp4_client_class WHERE name = 'short-lease'"
        )->fetchColumn();
        $db->prepare(
            'INSERT IGNORE INTO dhcp4_client_class_server (class_id, server_id, modification_ts)
             VALUES (?, 1, ?)'
        )->execute([$classId, $now]);

        // IDs gérés par DHCPMan (actifs + inactifs) pour nettoyer proprement
        $allIds = $db->query('SELECT kea_subnet_id FROM app_subnets')
                     ->fetchAll(PDO::FETCH_COLUMN);

        if ($allIds) {
            $in = implode(',', array_map('intval', $allIds));
            // Supprimer les enfants avant dhcp4_subnet pour éviter que le trigger BDEL
            // ne déclenche une chaîne qui tente d'UPDATEer dhcp4_subnet (interdit par MySQL).
            $db->exec("DELETE FROM dhcp4_options WHERE dhcp4_subnet_id IN ($in) AND scope_id = 1");
            $db->exec("DELETE FROM dhcp4_pool WHERE subnet_id IN ($in)");
            $db->exec("DELETE FROM dhcp4_subnet_server WHERE subnet_id IN ($in)");
            $db->exec("DELETE FROM dhcp4_subnet WHERE subnet_id IN ($in)");
        }

        $subnets       = $db->query('SELECT * FROM app_subnets WHERE is_active = 1 ORDER BY kea_subnet_id')->fetchAll();
        $validLifetime = (int)Settings::get('default_lease_time', '86400');
        $relayMode     = Settings::get('relay_mode', '0') === '1';
        $relayIface    = Settings::get('relay_interface', '');

        foreach ($subnets as $s) {
            $sid = (int)$s['kea_subnet_id'];

            if ($relayMode) {
                // En mode relay : interface = interface relay globale, relay JSON avec GIADDR
                $kea_interface = $relayIface ?: null;
                $relayJson     = $s['relay_address']
                    ? json_encode([$s['relay_address']])
                    : null;
            } else {
                $kea_interface = $s['interface'] ?: null;
                $relayJson     = null;
            }

            $db->prepare(
                'INSERT INTO dhcp4_subnet (subnet_id, subnet_prefix, interface, relay, valid_lifetime, modification_ts)
                 VALUES (?, ?, ?, ?, ?, ?)'
            )->execute([$sid, $s['cidr'], $kea_interface, $relayJson, $validLifetime, $now]);

            $db->prepare(
                'INSERT INTO dhcp4_subnet_server (subnet_id, server_id, modification_ts) VALUES (?, 1, ?)'
            )->execute([$sid, $now]);

            if ($s['has_dynamic_pool'] && $s['range_start'] && $s['range_end']) {
                $db->prepare(
                    'INSERT INTO dhcp4_pool (start_address, end_address, subnet_id, modification_ts)
                     VALUES (INET_ATON(?), INET_ATON(?), ?, ?)'
                )->execute([$s['range_start'], $s['range_end'], $sid, $now]);
            }

            // Pool short-lease (si défini sur ce subnet)
            if (!empty($s['short_lease_pool_start']) && !empty($s['short_lease_pool_end'])) {
                $db->prepare(
                    'INSERT INTO dhcp4_pool (start_address, end_address, subnet_id, client_classes, modification_ts)
                     VALUES (INET_ATON(?), INET_ATON(?), ?, \'["short-lease"]\', ?)'
                )->execute([
                    $s['short_lease_pool_start'],
                    $s['short_lease_pool_end'],
                    $sid,
                    $now,
                ]);
            }

            if ($s['gateway']) {
                $db->prepare(
                    "INSERT INTO dhcp4_options (code, formatted_value, space, persistent, dhcp4_subnet_id, scope_id, modification_ts)
                     VALUES (3, ?, 'dhcp4', 1, ?, 1, ?)"
                )->execute([$s['gateway'], $sid, $now]);
            }

            if ($s['dns_servers']) {
                $db->prepare(
                    "INSERT INTO dhcp4_options (code, formatted_value, space, persistent, dhcp4_subnet_id, scope_id, modification_ts)
                     VALUES (6, ?, 'dhcp4', 1, ?, 1, ?)"
                )->execute([$s['dns_servers'], $sid, $now]);
            }
        }
    }

    /** Retourne le premier ID Kea subnet disponible (sans trou si possible). */
    public static function nextAvailableId(): int {
        $ids = Database::get()->query(
            'SELECT kea_subnet_id FROM app_subnets ORDER BY kea_subnet_id'
        )->fetchAll(PDO::FETCH_COLUMN);

        $next = 1;
        foreach ($ids as $id) {
            if ((int)$id === $next) {
                $next++;
            } else {
                break;
            }
        }
        return $next;
    }

    /** Valide les données d'un subnet, retourne un tableau d'erreurs (vide si OK). */
    public static function validate(array $data, ?int $excludeId = null): array {
        $errors = [];
        if (empty($data['name'])) {
            $errors[] = 'Le nom est obligatoire.';
        }
        if (empty($data['kea_subnet_id']) || !ctype_digit((string)$data['kea_subnet_id'])) {
            $errors[] = 'L\'ID Kea subnet doit être un entier positif.';
        }
        if (empty($data['cidr']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/', $data['cidr'])) {
            $errors[] = 'Le CIDR est invalide (ex: 192.168.1.0/24).';
        }
        if (!empty($data['gateway']) && !isValidIp($data['gateway'])) {
            $errors[] = 'L\'adresse gateway est invalide.';
        }
        $relayMode = Settings::get('relay_mode', '0') === '1';
        if ($relayMode) {
            if (empty($data['relay_address'])) {
                $errors[] = 'L\'adresse relay (GIADDR) est obligatoire en mode relay.';
            } elseif (!isValidIp($data['relay_address'])) {
                $errors[] = 'L\'adresse relay (GIADDR) est invalide.';
            }
        } elseif (!empty($data['relay_address']) && !isValidIp($data['relay_address'])) {
            $errors[] = 'L\'adresse relay (GIADDR) est invalide.';
        }
        if (!empty($data['range_start']) && !isValidIp($data['range_start'])) {
            $errors[] = 'L\'IP de début de pool est invalide.';
        }
        if (!empty($data['range_end']) && !isValidIp($data['range_end'])) {
            $errors[] = 'L\'IP de fin de pool est invalide.';
        }

        // Pool short-lease (optionnel — les deux champs doivent être renseignés ensemble)
        $slStart = trim($data['short_lease_pool_start'] ?? '');
        $slEnd   = trim($data['short_lease_pool_end']   ?? '');
        if ($slStart !== '' || $slEnd !== '') {
            if ($slStart === '' || $slEnd === '') {
                $errors[] = 'Le pool short-lease requiert une IP de début et une IP de fin.';
            } else {
                if (!isValidIp($slStart)) {
                    $errors[] = 'Pool short-lease : IP de début invalide.';
                }
                if (!isValidIp($slEnd)) {
                    $errors[] = 'Pool short-lease : IP de fin invalide.';
                }
                if (isValidIp($slStart) && isValidIp($slEnd)) {
                    if (ipToInt($slStart) >= ipToInt($slEnd)) {
                        $errors[] = 'Pool short-lease : l\'IP de début doit être inférieure à l\'IP de fin.';
                    }
                    // Vérifier que les IPs appartiennent au CIDR
                    if (!empty($data['cidr'])) {
                        if (!ipInCidr($slStart, $data['cidr'])) {
                            $errors[] = 'Pool short-lease : l\'IP de début n\'appartient pas au subnet ' . $data['cidr'] . '.';
                        }
                        if (!ipInCidr($slEnd, $data['cidr'])) {
                            $errors[] = 'Pool short-lease : l\'IP de fin n\'appartient pas au subnet ' . $data['cidr'] . '.';
                        }
                    }
                    // Vérifier le non-chevauchement avec le pool dynamique principal
                    $mainStart = trim($data['range_start'] ?? '');
                    $mainEnd   = trim($data['range_end']   ?? '');
                    if (!empty($data['has_dynamic_pool']) && $mainStart && $mainEnd
                        && isValidIp($mainStart) && isValidIp($mainEnd)
                        && isValidIp($slStart) && isValidIp($slEnd)) {
                        $slStartInt  = ipToInt($slStart);
                        $slEndInt    = ipToInt($slEnd);
                        $mainStartInt = ipToInt($mainStart);
                        $mainEndInt   = ipToInt($mainEnd);
                        if ($slStartInt <= $mainEndInt && $slEndInt >= $mainStartInt) {
                            $errors[] = 'Le pool short-lease chevauche le pool dynamique principal ('
                                . $mainStart . ' – ' . $mainEnd . ').';
                        }
                    }
                }
            }
        }

        // Unicité kea_subnet_id
        if (!empty($data['kea_subnet_id'])) {
            $stmt = Database::get()->prepare(
                'SELECT id FROM app_subnets WHERE kea_subnet_id = ?' .
                ($excludeId ? ' AND id <> ' . (int)$excludeId : '')
            );
            $stmt->execute([(int)$data['kea_subnet_id']]);
            if ($stmt->fetch()) {
                $errors[] = 'Ce Kea subnet ID est déjà utilisé.';
            }
        }
        return $errors;
    }
}
