// Peers page controller. List + add + remove peer records. The data behind
// it is generated by scripts/webui/data/generators/peers/webui_peers.sh and
// served at /data/peers/generated/peers.json — Phase 2 only knows about
// kind=backup-channel; the other kinds light up in Phase 3.

class PeersPage {
    constructor() {
        this.peers = [];
        this.backupLocations = [];        // populated for the loc_idx dropdown
        this.taskManager = (typeof TaskManager !== 'undefined') ? new TaskManager() : null;
        this.eventBound = false;
    }

    async init() {
        this.bindEvents();
        await this.refreshAll();
        this.render();
    }

    async refreshAll() {
        const ts = Date.now();
        const [peersData, backupLocs] = await Promise.all([
            this.fetchJson(`/data/peers/generated/peers.json?t=${ts}`),
            this.fetchJson(`/data/backup/generated/locations.json?t=${ts}`)
        ]);
        this.peers = peersData?.peers || [];
        this.backupLocations = (backupLocs?.locations || []).filter(l => l.enabled);
    }

    async fetchJson(url) {
        try { const r = await fetch(url); if (!r.ok) return null; return await r.json(); }
        catch { return null; }
    }

    bindEvents() {
        if (this.eventBound) return;
        this.eventBound = true;

        document.addEventListener('click', (e) => {
            if (e.target.closest('#peers-refresh-btn')) {
                this.runTask('libreportal regen webui --force', 'webui', null);
                setTimeout(() => this.refreshAll().then(() => this.render()), 2000);
                return;
            }
            if (e.target.closest('#peers-add-btn')) {
                this.openAddModal();
                return;
            }
            if (e.target.closest('#peers-add-confirm')) {
                this.confirmAdd();
                return;
            }
            const removeBtn = e.target.closest('[data-action="peer-remove"]');
            if (removeBtn) {
                this.removePeer(removeBtn.dataset.name);
                return;
            }
            const checkBtn = e.target.closest('[data-action="peer-check"]');
            if (checkBtn) {
                this.checkPeer(checkBtn.dataset.name);
                return;
            }
            if (e.target.closest('[data-close-modal]') || e.target.matches('.backup-modal')) {
                this.closeAllModals();
                return;
            }
        });
    }

    render() {
        const list  = document.getElementById('peers-list');
        const empty = document.getElementById('peers-empty');
        if (!list || !empty) return;

        if (!this.peers.length) {
            list.innerHTML = '';
            empty.hidden = false;
            return;
        }
        empty.hidden = true;

        list.innerHTML = this.peers.map(p => this.renderPeerCard(p)).join('');
    }

    renderPeerCard(peer) {
        const cfg = peer.config || {};
        const cfgSummary = this.summariseConfig(peer.kind, cfg);
        const statusClass = {
            ok: 'ok',
            'no-snapshots': 'warn',
            'config-error': 'fail',
            'not-yet-implemented': 'warn',
            'unknown-kind': 'fail',
            unknown: 'none'
        }[peer.status] || 'none';
        const statusLabel = {
            ok: 'Reachable',
            'no-snapshots': 'No recent snapshots',
            'config-error': 'Config error',
            'not-yet-implemented': 'Not yet supported',
            'unknown-kind': 'Unknown kind',
            unknown: 'Not checked'
        }[peer.status] || peer.status;

        return `
            <div class="backup-location-row" style="margin-bottom:10px; padding:14px 18px; background:var(--surface-2, #1a1a1a); border-radius:8px">
                <div style="display:flex; justify-content:space-between; align-items:flex-start; gap:14px">
                    <div style="min-width:0; flex:1">
                        <div style="display:flex; align-items:center; gap:10px; margin-bottom:4px">
                            <strong style="font-size:1.1em">${this.escape(peer.name)}</strong>
                            <span class="backup-card-hint" style="opacity:.7">(${this.escape(peer.kind)})</span>
                            <span class="backup-status-dot ${statusClass}" title="${this.escape(statusLabel)}"></span>
                            <span class="backup-card-hint" style="font-size:.8em">${this.escape(statusLabel)}</span>
                        </div>
                        <div class="backup-card-hint" style="font-size:.9em">${cfgSummary}</div>
                        ${peer.last_seen ? `<div class="backup-card-hint" style="font-size:.78em; margin-top:4px">Checked ${this.escape(this.formatRelativeTime(peer.last_seen))}</div>` : ''}
                    </div>
                    <div style="display:flex; gap:8px; flex-shrink:0">
                        <button class="backup-secondary-btn" data-action="peer-check" data-name="${this.escape(peer.name)}">Check</button>
                        <button class="backup-danger-btn" data-action="peer-remove" data-name="${this.escape(peer.name)}">Remove</button>
                    </div>
                </div>
            </div>
        `;
    }

    summariseConfig(kind, cfg) {
        if (kind === 'backup-channel') {
            const host = cfg.hostname ? `hostname=<code>${this.escape(cfg.hostname)}</code>` : '<em>no hostname set</em>';
            const loc  = cfg.loc_idx != null ? `location <code>${this.escape(String(cfg.loc_idx))}</code>` : 'any enabled location';
            return `${host} · ${loc}`;
        }
        // direct-ssh kinds: minimal placeholder until Phase 3.
        return Object.keys(cfg).length
            ? Object.entries(cfg).map(([k, v]) => `${this.escape(k)}=<code>${this.escape(String(v))}</code>`).join(' · ')
            : '<em>(no config)</em>';
    }

    openAddModal() {
        const modal = document.getElementById('peers-add-modal');
        const body  = document.getElementById('peers-add-modal-body');
        if (!modal || !body) return;

        const locOptions = ['<option value="">Any enabled location</option>']
            .concat(this.backupLocations.map(l =>
                `<option value="${l.idx}">${this.escape(l.name || `Location ${l.idx}`)} (idx ${l.idx})</option>`))
            .join('');

        body.innerHTML = `
            <div class="backup-form-grid">
                <div>
                    <label for="peer-add-name">Name</label>
                    <input type="text" class="form-control" id="peer-add-name" placeholder="e.g. homelab">
                    <span class="backup-card-hint">Used as the label everywhere — letters, digits, ._- only.</span>
                </div>
                <div>
                    <label for="peer-add-kind">Kind</label>
                    <select class="form-control" id="peer-add-kind">
                        <option value="backup-channel" selected>Backup channel (shared backup location)</option>
                        <option value="direct-ssh-direct" disabled>Direct SSH (Phase 3)</option>
                        <option value="direct-ssh-via-relay" disabled>Via Connect relay (Phase 3b)</option>
                    </select>
                </div>
                <div>
                    <label for="peer-add-hostname">Source hostname</label>
                    <input type="text" class="form-control" id="peer-add-hostname" placeholder="Hostname as it appears in restic snapshots">
                    <span class="backup-card-hint">Match the source LibrePortal's CFG_INSTALL_NAME (or hostname if that's unset).</span>
                </div>
                <div>
                    <label for="peer-add-loc">Preferred location</label>
                    <select class="form-control" id="peer-add-loc">${locOptions}</select>
                    <span class="backup-card-hint">Pinning a location skips probing the others for snapshots.</span>
                </div>
            </div>
        `;
        modal.classList.add('open');
    }

    async confirmAdd() {
        const modal = document.getElementById('peers-add-modal');
        if (!modal) return;
        const name = modal.querySelector('#peer-add-name')?.value?.trim();
        const kind = modal.querySelector('#peer-add-kind')?.value || 'backup-channel';
        const host = modal.querySelector('#peer-add-hostname')?.value?.trim();
        const loc  = modal.querySelector('#peer-add-loc')?.value;

        if (!name) { this.notify('Name is required.', 'error'); return; }
        if (!host) { this.notify('Hostname is required for backup-channel peers.', 'error'); return; }
        if (!/^[A-Za-z0-9._-]+$/.test(name)) {
            this.notify('Name must use letters, digits, dot, underscore or dash.', 'error');
            return;
        }

        this.closeAllModals();
        const cfgPairs = [`hostname=${host}`];
        if (loc) cfgPairs.push(`loc_idx=${loc}`);
        const cmd = `libreportal peer add ${name} ${kind} ${cfgPairs.join(' ')}`;
        await this.runTask(cmd, 'peer', null);
        setTimeout(() => this.refreshAll().then(() => this.render()), 1500);
    }

    async removePeer(name) {
        if (!confirm(`Remove peer "${name}"?\n\nThis only removes the local label — backups and the other host are untouched.`)) return;
        await this.runTask(`libreportal peer remove ${name}`, 'peer', null);
        setTimeout(() => this.refreshAll().then(() => this.render()), 1500);
    }

    async checkPeer(name) {
        await this.runTask(`libreportal peer check ${name}`, 'peer', null);
        setTimeout(() => this.refreshAll().then(() => this.render()), 2000);
    }

    closeAllModals() {
        document.querySelectorAll('.backup-modal.open').forEach(m => m.classList.remove('open'));
    }

    async runTask(command, type, app) {
        if (!this.taskManager) {
            this.notify('Task system unavailable', 'error');
            return;
        }
        try {
            await this.taskManager.createTask(command, type, app);
        } catch (err) {
            this.notify(`Failed to queue task: ${err.message || err}`, 'error');
        }
    }

    notify(message, kind) {
        if (typeof window.showNotification === 'function') window.showNotification(message, kind);
        else if (kind === 'error') console.error(message);
        else console.log(message);
    }

    formatRelativeTime(iso) {
        if (!iso) return 'never';
        const t = Date.parse(iso);
        if (!t) return iso;
        const diff = Date.now() - t;
        const minute = 60_000, hour = 60 * minute, day = 24 * hour;
        if (diff < hour)    return `${Math.max(1, Math.round(diff / minute))} min ago`;
        if (diff < day)     return `${Math.round(diff / hour)} h ago`;
        if (diff < 7 * day) return `${Math.round(diff / day)} d ago`;
        return new Date(t).toISOString().slice(0, 10);
    }

    escape(s) {
        return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({
            '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
        }[c]));
    }
}

window.PeersPage = PeersPage;
