// retoor <retoor@molodetz.nl>

import { Http } from "./Http.js";
import { Poller } from "./Poller.js";
import { JobPoller } from "./JobPoller.js";
import { DateFormat } from "./DateFormat.js";

class BackupMonitor {
    constructor() {
        this.root = document.getElementById("backups-root");
        this.generated = document.querySelector("[data-meta='generated']");
        this.runForm = document.getElementById("backup-run-form");
        this.scheduleForm = document.getElementById("backup-schedule-form");
        this.scheduleModal = document.getElementById("backup-schedule-modal");
        this.scheduleKind = document.getElementById("backup-schedule-kind");
        this.scheduleTitle = document.getElementById("backup-schedule-title");
        this.pollMs = 30000;
        this.targets = [];
        this.canDownload = false;
    }

    start() {
        if (!this.root) return;
        this.bindRunForm();
        this.bindScheduleForm();
        this.bindActions();
        this.subscribe();
        this.poller = new Poller(() => this.poll(), this.pollMs, { pauseHidden: true });
    }

    subscribe() {
        const pubsub = window.app && window.app.pubsub;
        if (!pubsub) return;
        if (this.unsubscribe) this.unsubscribe();
        this.unsubscribe = pubsub.subscribe("admin.backups", (data) => this.applyPush(data));
    }

    applyPush(data) {
        this.targets = data.targets || this.targets;
        this.render(data);
    }

    async poll() {
        try {
            const data = await Http.getJson("/admin/backups/data");
            this.canDownload = !!data.can_download_backups;
            this.targets = data.targets || [];
            this.render(data);
        } catch {
        }
    }

    bindRunForm() {
        if (!this.runForm) return;
        this.runForm.addEventListener("submit", async (event) => {
            event.preventDefault();
            const target = this.runForm.querySelector("[name='target']").value;
            const button = this.runForm.querySelector("button[type='submit']");
            button.disabled = true;
            try {
                const result = await Http.send("/admin/backups/run", { target });
                window.app.toast.show("Backup started", { type: "info" });
                this.followJob(result.status_url);
            } catch {
            } finally {
                button.disabled = false;
            }
        });
    }

    followJob(statusUrl) {
        if (!statusUrl) return;
        this.poll();
        JobPoller.run(statusUrl, {
            onDone: () => {
                window.app.toast.show("Backup completed", { type: "success" });
                this.poll();
            },
            onFailed: (status) => {
                Http.notifyError((status && status.error) || "Backup failed");
                this.poll();
            },
        });
    }

    bindScheduleForm() {
        if (!this.scheduleForm) return;
        const newButton = document.getElementById("backup-schedule-new");
        if (newButton) {
            newButton.addEventListener("click", () => this.resetScheduleForm());
        }
        if (this.scheduleKind) {
            this.scheduleKind.addEventListener("change", () => this.syncScheduleKind());
        }
        this.scheduleForm.addEventListener("submit", async (event) => {
            event.preventDefault();
            const params = Object.fromEntries(new FormData(this.scheduleForm).entries());
            try {
                await Http.send(this.scheduleForm.action, params);
                this.scheduleModal.classList.remove("visible");
                window.app.toast.show("Schedule saved", { type: "success" });
                this.poll();
            } catch {
            }
        });
    }

    syncScheduleKind() {
        const kind = this.scheduleKind.value;
        this.scheduleForm.querySelectorAll("[data-kind]").forEach((node) => {
            node.hidden = node.dataset.kind !== kind;
        });
    }

    resetScheduleForm() {
        this.scheduleForm.reset();
        this.scheduleForm.action = "/admin/backups/schedules/create";
        this.scheduleTitle.textContent = "New backup schedule";
        this.syncScheduleKind();
    }

    openScheduleEdit(schedule) {
        const form = this.scheduleForm;
        form.action = `/admin/backups/schedules/${schedule.uid}/edit`;
        form.querySelector("[name='name']").value = schedule.name || "";
        form.querySelector("[name='target']").value = schedule.target || "full";
        form.querySelector("[name='kind']").value = schedule.kind || "interval";
        form.querySelector("[name='every_seconds']").value = schedule.every_seconds || 86400;
        form.querySelector("[name='cron']").value = schedule.cron || "";
        form.querySelector("[name='keep_last']").value = schedule.keep_last || 0;
        this.scheduleTitle.textContent = `Edit schedule: ${schedule.name || ""}`;
        this.syncScheduleKind();
        this.scheduleModal.classList.add("visible");
    }

    bindActions() {
        this.root.addEventListener("click", async (event) => {
            const button = event.target.closest("[data-action]");
            if (!button) return;
            const action = button.dataset.action;
            const uid = button.dataset.uid;
            if (action === "edit-schedule") {
                const schedule = this.schedulesByUid[uid];
                if (schedule) this.openScheduleEdit(schedule);
                return;
            }
            const prompts = {
                "delete-backup": "Permanently delete this backup archive?",
                "delete-schedule": "Delete this backup schedule?",
                "toggle-schedule": null,
                "run-schedule": null,
            };
            const message = prompts[action];
            if (message) {
                const ok = await window.app.dialog.confirm({ message, danger: true });
                if (!ok) return;
            }
            const urls = {
                "delete-backup": `/admin/backups/${uid}/delete`,
                "delete-schedule": `/admin/backups/schedules/${uid}/delete`,
                "toggle-schedule": `/admin/backups/schedules/${uid}/toggle`,
                "run-schedule": `/admin/backups/schedules/${uid}/run`,
            };
            try {
                await Http.send(urls[action], {});
                this.poll();
            } catch {
            }
        });
    }

    el(tag, className, text) {
        const node = document.createElement(tag);
        if (className) node.className = className;
        if (text !== undefined) node.textContent = String(text);
        return node;
    }

    cards(items) {
        const grid = this.el("div", "metric-cards");
        for (const item of items) {
            const card = this.el("div", "metric-card");
            card.appendChild(this.el("span", "metric-value", item.value));
            card.appendChild(this.el("span", "metric-label", item.label));
            grid.appendChild(card);
        }
        return grid;
    }

    section(title, ...nodes) {
        const wrap = this.el("div", "backups-section");
        wrap.appendChild(this.el("h3", null, title));
        for (const node of nodes) if (node) wrap.appendChild(node);
        return wrap;
    }

    storageSection(storage) {
        const disk = storage.disk || {};
        const totals = storage.backups_total || {};
        const data = storage.data_dir || {};
        const overview = this.cards([
            { label: "Disk used", value: `${disk.used_human || "0"} / ${disk.total_human || "0"}` },
            { label: "Disk free", value: disk.free_human || "0" },
            { label: "Disk usage", value: `${disk.used_percent || 0}%` },
            { label: "Data directory", value: data.size_human || "0" },
            { label: "Backups stored", value: totals.count || 0 },
            { label: "Backups size", value: totals.size_human || "0" },
        ]);
        const table = this.el("table", "metric-table");
        const head = this.el("thead");
        const headRow = this.el("tr");
        for (const col of ["Path", "Size", "Files", "Location"]) headRow.appendChild(this.el("th", null, col));
        head.appendChild(headRow);
        table.appendChild(head);
        const body = this.el("tbody");
        for (const path of storage.paths || []) {
            const tr = this.el("tr");
            tr.appendChild(this.el("td", null, path.label));
            tr.appendChild(this.el("td", null, path.size_human));
            tr.appendChild(this.el("td", null, path.file_count));
            const location = this.el("td", "backups-path", path.path);
            if (!path.exists) location.classList.add("backups-missing");
            tr.appendChild(location);
            body.appendChild(tr);
        }
        table.appendChild(body);
        return this.section("Storage", overview, table);
    }

    backupsSection(backups, metrics) {
        const overview = this.cards([
            { label: "Total", value: metrics.total || 0 },
            { label: "Done", value: metrics.done || 0 },
            { label: "Running", value: metrics.running || 0 },
            { label: "Pending", value: metrics.pending || 0 },
            { label: "Failed", value: metrics.failed || 0 },
            { label: "Archive size", value: metrics.size_human || "0" },
        ]);
        if (!backups.length) {
            return this.section("Backups", overview, this.el("p", "admin-empty", "No backups yet."));
        }
        const table = this.el("table", "metric-table backups-table");
        const head = this.el("thead");
        const headRow = this.el("tr");
        for (const col of ["Created", "Target", "Status", "Size", "Files", "Checksum", "Actions"]) {
            headRow.appendChild(this.el("th", null, col));
        }
        head.appendChild(headRow);
        table.appendChild(head);
        const body = this.el("tbody");
        for (const backup of backups) {
            const tr = this.el("tr");
            tr.appendChild(this.el("td", null, DateFormat.format(backup.created_at, true)));
            tr.appendChild(this.el("td", null, backup.label || backup.target));
            const status = this.el("td");
            status.appendChild(this.el("span", `backups-status backups-status-${backup.status}`, backup.status));
            tr.appendChild(status);
            tr.appendChild(this.el("td", null, backup.size_human || "-"));
            tr.appendChild(this.el("td", null, backup.file_count || 0));
            tr.appendChild(this.el("td", "backups-sha", backup.sha256 ? backup.sha256.slice(0, 12) : "-"));
            tr.appendChild(this.backupActions(backup));
            body.appendChild(tr);
        }
        table.appendChild(body);
        return this.section("Backups", overview, table);
    }

    backupActions(backup) {
        const cell = this.el("td", "backups-actions");
        if (backup.status === "done") {
            if (this.canDownload) {
                const link = this.el("a", "admin-btn admin-btn-sm", "Download");
                link.href = `/admin/backups/${backup.uid}/download`;
                cell.appendChild(link);
            } else {
                const blocked = this.el("button", "admin-btn admin-btn-sm", "Download");
                blocked.disabled = true;
                blocked.setAttribute("aria-disabled", "true");
                blocked.title = "Not available";
                cell.appendChild(blocked);
            }
        }
        const remove = this.el("button", "admin-btn admin-btn-sm admin-btn-danger", "Delete");
        remove.dataset.action = "delete-backup";
        remove.dataset.uid = backup.uid;
        cell.appendChild(remove);
        return cell;
    }

    schedulesSection(schedules) {
        this.schedulesByUid = {};
        for (const schedule of schedules) this.schedulesByUid[schedule.uid] = schedule;
        if (!schedules.length) {
            return this.section("Schedules", this.el("p", "admin-empty", "No backup schedules configured."));
        }
        const table = this.el("table", "metric-table backups-schedules");
        const head = this.el("thead");
        const headRow = this.el("tr");
        for (const col of ["Name", "Target", "Trigger", "Keep", "Next run", "Last run", "Runs", "State", "Actions"]) {
            headRow.appendChild(this.el("th", null, col));
        }
        head.appendChild(headRow);
        table.appendChild(head);
        const body = this.el("tbody");
        for (const schedule of schedules) {
            const tr = this.el("tr");
            tr.appendChild(this.el("td", null, schedule.name));
            tr.appendChild(this.el("td", null, schedule.target));
            const trigger = schedule.kind === "cron" ? `cron ${schedule.cron}` : `every ${schedule.every_seconds}s`;
            tr.appendChild(this.el("td", null, trigger));
            tr.appendChild(this.el("td", null, schedule.keep_last || "all"));
            tr.appendChild(this.el("td", null, schedule.next_run_at ? DateFormat.format(schedule.next_run_at, true) : "-"));
            tr.appendChild(this.el("td", null, schedule.last_run_at ? DateFormat.format(schedule.last_run_at, true) : "-"));
            tr.appendChild(this.el("td", null, schedule.run_count || 0));
            const state = this.el("td");
            state.appendChild(this.el("span", `backups-status backups-status-${schedule.enabled ? "done" : "failed"}`, schedule.enabled ? "enabled" : "disabled"));
            tr.appendChild(state);
            tr.appendChild(this.scheduleActions(schedule));
            body.appendChild(tr);
        }
        table.appendChild(body);
        return this.section("Schedules", table);
    }

    scheduleActions(schedule) {
        const cell = this.el("td", "backups-actions");
        const definitions = [
            ["run-schedule", "Run", ""],
            ["edit-schedule", "Edit", ""],
            ["toggle-schedule", schedule.enabled ? "Disable" : "Enable", ""],
            ["delete-schedule", "Delete", "admin-btn-danger"],
        ];
        for (const [action, label, extra] of definitions) {
            const button = this.el("button", `admin-btn admin-btn-sm ${extra}`.trim(), label);
            button.dataset.action = action;
            button.dataset.uid = schedule.uid;
            cell.appendChild(button);
        }
        return cell;
    }

    render(data) {
        if (this.generated && data.generated_at) {
            this.generated.textContent = `Updated ${DateFormat.format(data.generated_at, true)}`;
        }
        this.root.textContent = "";
        this.root.appendChild(this.storageSection(data.storage || {}));
        this.root.appendChild(this.backupsSection(data.backups || [], data.metrics || {}));
        this.root.appendChild(this.schedulesSection(data.schedules || []));
    }
}

window.BackupMonitor = BackupMonitor;
