// The firewall, led by what another machine can actually reach.
//
// A rules list alone is not an answer: a port is reachable only when something
// is listening on a network address AND the firewall permits it. On this
// machine that crossing is the whole story -- the rules look unremarkable while
// a database and a cache sit open, because Fedora Workstation's zone opens
// every port above 1024 and rootless containers publish on all interfaces.
//
// Rich rules are shown but never edited. They are a syntax rather than a
// setting, and a page that half-supports a syntax is a trap -- but hiding them
// would mean the page misrepresents the configuration.

import Quickshell
import QtQuick
import qs.config
import qs.services

SettingsPage {
    id: root

    objectName: "firewall"
    title: "Firewall"
    lede: "What another machine on your network can reach, and what allows it."

    property string confirmingRemoval: ""
    property bool confirmingRange: false

    Component.onCompleted: Firewall.refresh()

    TextRow {
        visible: Firewall.lastError !== ""
        label: "The firewall needs attention"
        detail: Firewall.lastError
        value: ""
        divider: false
    }

    // ── The finding, when there is one ───────────────────────────────────────

    SettingsCard {
        visible: Firewall.exposedDataStores.length > 0
        title: Firewall.exposedDataStores.length === 1
            ? "A database is reachable from your network"
            : "Databases are reachable from your network"
        subtitle: {
            const names = Firewall.exposedDataStores.map(entry => String(entry.name));
            return names.join(" and ") + " "
                + (names.length === 1 ? "is" : "are")
                + " listening on every interface, and this zone permits it. Anyone on your network can connect.";
        }

        Repeater {
            model: Firewall.exposedDataStores

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.name ?? "")
                detail: "Port " + modelData.port + "/" + String(modelData.protocol ?? "")
                    + (String(modelData.process ?? "") !== ""
                        ? " · " + String(modelData.process) : "")
                    + " · allowed by " + String(modelData.allowedBy ?? "")
                value: ""
                divider: index < Firewall.exposedDataStores.length - 1
            }
        }
    }

    // ── Everything reachable ─────────────────────────────────────────────────

    SettingsCard {
        title: "Reachable right now"
        subtitle: !Firewall.scanned
            ? "Checking what is listening and what the firewall permits…"
            : (Firewall.available
                ? "Listening on a network address, and permitted by the firewall. Both have to be true."
                : "The firewall is not running, so nothing here is being filtered.")

        Repeater {
            model: Firewall.exposed

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.name ?? "")
                detail: "Port " + modelData.port + "/" + String(modelData.protocol ?? "")
                    + (String(modelData.process ?? "") !== "" && String(modelData.process) !== String(modelData.name)
                        ? " · " + String(modelData.process) : "")
                    + " · allowed by " + String(modelData.allowedBy ?? "")
                value: String(modelData.kind ?? "") === "data" ? "Database" : ""
                divider: index < Firewall.exposed.length - 1
            }
        }

        TextRow {
            visible: Firewall.exposed.length === 0 && Firewall.scanned && Firewall.available
            label: "Nothing is reachable"
            detail: "No service is both listening on a network address and permitted"
            value: ""
            divider: false
        }
    }

    // ── The rules that allow it ──────────────────────────────────────────────

    SettingsCard {
        visible: Firewall.zone !== null
        title: "What this zone allows"
        subtitle: Firewall.zone
            ? String(Firewall.zone.name) + ", applied to "
              + (Firewall.zone.interfaces ?? []).join(" and ")
            : ""

        // The single rule that explains almost every row above.
        Column {
            width: parent.width
            visible: Firewall.wideOpen

            SettingRow {
                width: parent.width
                label: "Ports " + Firewall.openRanges.join(", ")
                detail: root.confirmingRange
                    ? "Closing this cuts off " + Firewall.rangeDependents().length
                      + " reachable service" + (Firewall.rangeDependents().length === 1 ? "" : "s")
                      + ", including " + Firewall.rangeDependents().slice(0, 3)
                        .map(entry => String(entry.name)).join(", ")
                      + ". Anything that needs a port will have to be allowed by name."
                    : "Fedora Workstation opens these so applications can listen without asking. It is why most of the list above is reachable."
                controlWidth: 230

                Row {
                    anchors.right: parent.right
                    anchors.verticalCenter: parent.verticalCenter
                    spacing: 8

                    SettingsButton {
                        text: root.confirmingRange ? "Keep it open" : "Close the range…"
                        enabled: !Firewall.busy
                        onClicked: root.confirmingRange = !root.confirmingRange
                    }

                    SettingsButton {
                        visible: root.confirmingRange
                        text: "Close it"
                        tone: "danger"
                        enabled: !Firewall.busy
                        onClicked: {
                            root.confirmingRange = false;
                            for (const spec of Firewall.openRanges)
                                Firewall.removePort(String(spec));
                        }
                    }
                }
            }
        }

        Repeater {
            model: Firewall.zone?.services ?? []

            delegate: SettingRow {
                id: serviceRow

                required property var modelData
                required property int index

                readonly property string serviceName: String(serviceRow.modelData)
                readonly property bool confirming: root.confirmingRemoval === serviceRow.serviceName
                // Removing ssh while someone is connected over it ends their
                // session. Worth saying before, not after.
                readonly property bool risky: serviceRow.serviceName === "ssh"
                    && Firewall.sshSessions > 0

                width: parent.width
                label: serviceRow.serviceName
                detail: serviceRow.confirming
                    ? (serviceRow.risky
                        ? "Someone is connected over SSH right now. Removing this ends that session."
                        : "Anything relying on this service stops being reachable.")
                    : "Allowed by name, so it works whatever the port range says"
                controlWidth: 210
                divider: serviceRow.index < (Firewall.zone?.services ?? []).length - 1

                Row {
                    anchors.right: parent.right
                    anchors.verticalCenter: parent.verticalCenter
                    spacing: 8

                    SettingsButton {
                        text: serviceRow.confirming ? "Keep" : "Remove…"
                        enabled: !Firewall.busy
                        onClicked: root.confirmingRemoval =
                            serviceRow.confirming ? "" : serviceRow.serviceName
                    }

                    SettingsButton {
                        visible: serviceRow.confirming
                        text: "Remove"
                        tone: "danger"
                        enabled: !Firewall.busy
                        onClicked: {
                            root.confirmingRemoval = "";
                            Firewall.removeService(serviceRow.serviceName);
                        }
                    }
                }
            }
        }

        // Shown, never edited.
        TextRow {
            visible: (Firewall.zone?.richRules ?? []).length > 0
            label: "Rich rules"
            detail: "Custom rules in firewalld's own syntax. Shown here so this page does not misrepresent your configuration; edit them with firewall-cmd."
            value: (Firewall.zone?.richRules ?? []).length + " defined"
            divider: false
        }
    }

    // ── Zones ────────────────────────────────────────────────────────────────

    SettingsCard {
        title: "Zones"
        subtitle: "A zone is a set of rules. Each network connection uses one."

        Repeater {
            model: Object.keys(Firewall.activeZones ?? ({}))

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData)
                detail: "Applied to " + (Firewall.activeZones[String(modelData)] ?? []).join(", ")
                value: String(modelData) === Firewall.defaultZone ? "Default" : ""
                divider: true
            }
        }

        TextRow {
            label: "Default for new connections"
            detail: "Used when a network does not ask for a particular zone"
            value: Firewall.defaultZone
            divider: false
        }
    }

    // ── The service underneath ───────────────────────────────────────────────

    SettingsCard {
        title: "Firewall service"

        TextRow {
            label: "firewalld"
            detail: !Firewall.scanned
                ? "Reading the firewall's state…"
                : (Firewall.running
                    ? (Firewall.enabledAtBoot
                        ? "Running, and starts with the system"
                        : "Running, but not started at boot")
                    : "Not running, so nothing is being filtered")
            value: !Firewall.scanned ? "Checking…" : (Firewall.running ? "Running" : "Stopped")
            divider: false
        }
    }
}
