// retoor <retoor@molodetz.nl>

import { Http } from "./Http.js";

export class GatewayAdmin {
    constructor(root) {
        this.root = root;
        this.providersBody = root.querySelector("#gw-providers");
        this.modelsBody = root.querySelector("#gw-models");
        this.providerForm = root.querySelector("#gw-provider-form");
        this.modelForm = root.querySelector("#gw-model-form");
        this.providerSelects = root.querySelectorAll("[data-provider-select]");
        this.providers = [];
        this.models = [];
        this.quotaRulesBody = root.querySelector("#gw-quota-rules");
        this.quotaForm = root.querySelector("#gw-quota-form");
        this.quotaCancelEdit = root.querySelector("#gw-quota-cancel-edit");
        this.quotaRules = [];
    }

    async start() {
        this.bind();
        this.syncKindFields();
        await this.reload();
    }

    syncKindFields() {
        const kind = this.modelForm.kind.value || "chat";
        const priceInputLabel = this.root.querySelector("#gw-model-price-input-label");
        if (priceInputLabel) {
            priceInputLabel.textContent =
                kind === "image"
                    ? "Price per image ($)"
                    : "Price input / 1M ($) (embed/vision)";
        }
        this.root.querySelectorAll("[data-kind-field]").forEach((field) => {
            const kinds = (field.dataset.kindField || "").split(/\s+/).filter(Boolean);
            field.style.display = kinds.includes(kind) ? "" : "none";
        });
        this.fillFallbackSelect(this.modelForm.source_model.value);
    }

    fillFallbackSelect(excludeSource) {
        const select = this.modelForm.fallback_model;
        if (!select) return;
        const kind = this.modelForm.kind.value || "chat";
        const current = select.value;
        const options = [`<option value="">(none)</option>`].concat(
            this.models
                .filter((m) => m.kind === kind && m.source_model !== excludeSource)
                .map(
                    (m) =>
                        `<option value="${this.attr(m.source_model)}">${this.escape(m.source_model)}</option>`
                )
        );
        select.innerHTML = options.join("");
        select.value = current;
    }

    bind() {
        this.providerForm.addEventListener("submit", (event) => {
            event.preventDefault();
            this.saveProvider();
        });
        this.modelForm.addEventListener("submit", (event) => {
            event.preventDefault();
            this.saveModel();
        });
        this.modelForm.kind.addEventListener("change", () => this.syncKindFields());
        this.providersBody.addEventListener("click", (event) => this.onProviderClick(event));
        this.modelsBody.addEventListener("click", (event) => this.onModelClick(event));
        this.quotaForm.addEventListener("submit", (event) => {
            event.preventDefault();
            this.saveQuotaRule();
        });
        this.quotaRulesBody.addEventListener("click", (event) => this.onQuotaRuleClick(event));
        this.quotaCancelEdit.addEventListener("click", () => this.resetQuotaForm());
    }

    notify(message, type) {
        if (window.app && window.app.toast) {
            window.app.toast.show(message, { type: type || "info" });
        }
    }

    async reload() {
        const providerCount = await this.loadProviders();
        const modelCount = await this.loadModels();
        const quotaCount = await this.loadQuotaRules();
        const count = this.root.querySelector("#gw-count");
        if (count) {
            count.textContent = `${providerCount} providers, ${modelCount} routes, ${quotaCount} quota rules`;
        }
    }

    escape(value) {
        const span = document.createElement("span");
        span.textContent = value == null ? "" : String(value);
        return span.innerHTML;
    }

    attr(value) {
        return this.escape(value).split('"').join("&quot;");
    }

    async loadProviders() {
        const data = await Http.getJson("/admin/gateway/providers");
        this.providers = data.providers || [];
        this.renderDefault(data.default || {});
        this.renderProviders();
        this.fillProviderSelects();
        return this.providers.length;
    }

    renderDefault(def) {
        const el = this.root.querySelector("#gw-default");
        if (!el) return;
        el.innerHTML = `
            <strong>default</strong> (from <a href="/admin/services">Services config</a>):
            chat <code class="gw-code">${this.escape(def.model)}</code> at <code class="gw-code">${this.escape(def.base_url)}</code>,
            embed <code class="gw-code">${this.escape(def.embed_model)}</code>,
            image <code class="gw-code">${this.escape(def.image_model)}</code>,
            vision <code class="gw-code">${this.escape(def.vision_model)}</code>`;
    }

    renderProviders() {
        if (!this.providers.length) {
            this.providersBody.innerHTML = `<tr><td colspan="4" class="admin-empty">No extra providers. Model routes with a blank provider use the default.</td></tr>`;
            return;
        }
        this.providersBody.innerHTML = this.providers
            .map(
                (p) => `<tr>
                    <td>${this.escape(p.name)}</td>
                    <td><code class="gw-code">${this.escape(p.base_url)}</code></td>
                    <td>${p.is_active ? "yes" : "no"}</td>
                    <td class="gw-actions">
                        <button class="admin-btn admin-btn-sm" data-edit-provider="${this.attr(p.name)}">Edit</button>
                        <button class="admin-btn admin-btn-sm admin-btn-danger" data-del-provider="${this.attr(p.name)}">Delete</button>
                    </td>
                </tr>`
            )
            .join("");
    }

    fillProviderSelects() {
        const options =
            `<option value="">default</option>` +
            this.providers.map((p) => `<option value="${this.attr(p.name)}">${this.escape(p.name)}</option>`).join("");
        this.providerSelects.forEach((select) => {
            const current = select.value;
            select.innerHTML = options;
            select.value = current;
        });
    }

    async loadModels() {
        const data = await Http.getJson("/admin/gateway/models");
        const models = data.models || [];
        this.models = models;
        this.fillFallbackSelect(this.modelForm.source_model.value);
        if (!models.length) {
            this.modelsBody.innerHTML = `<tr><td colspan="7" class="admin-empty">No model routes. Requests fall through to the default upstream.</td></tr>`;
            return 0;
        }
        this.modelsBody.innerHTML = models
            .map((m) => {
                const vision = m.vision_model
                    ? `<code class="gw-code">${this.escape(m.vision_provider || m.provider || "default")}/${this.escape(m.vision_model)}</code>`
                    : `<span class="gw-muted">-</span>`;
                const provider = m.provider || "default";
                const badges = [];
                if (m.context_tier_threshold_tokens) {
                    badges.push(`<span class="gw-badge" title="Tier-2 rates above ${m.context_tier_threshold_tokens} input tokens">tiered</span>`);
                }
                if (m.off_peak_start_minute !== null && m.off_peak_start_minute !== undefined) {
                    const start = this.minutesToTime(m.off_peak_start_minute);
                    const end = this.minutesToTime(m.off_peak_end_minute);
                    badges.push(`<span class="gw-badge" title="${m.off_peak_discount_pct}% off ${start}-${end} UTC">off-peak</span>`);
                }
                if (m.kind === "image" && m.price_input_per_m) {
                    badges.push(`<span class="gw-badge" title="Flat price per generated image">$${m.price_input_per_m}/img</span>`);
                }
                if (m.fallback_model) {
                    badges.push(`<span class="gw-badge" title="Falls back to ${this.escape(m.fallback_model)} on failure">fallback: ${this.escape(m.fallback_model)}</span>`);
                }
                const economy = badges.length ? badges.join(" ") : `<span class="gw-muted">-</span>`;
                return `<tr>
                    <td>${this.escape(m.source_model)}</td>
                    <td>${this.escape(provider)}</td>
                    <td><code class="gw-code">${this.escape(m.target_model)}</code></td>
                    <td>${this.escape(m.kind)}</td>
                    <td>${vision}</td>
                    <td>${economy}</td>
                    <td class="gw-actions">
                        <button class="admin-btn admin-btn-sm" data-edit-model='${this.attr(JSON.stringify(m))}'>Edit</button>
                        <button class="admin-btn admin-btn-sm admin-btn-danger" data-del-model="${this.attr(m.source_model)}">Delete</button>
                    </td>
                </tr>`;
            })
            .join("");
        return models.length;
    }

    formValues(form) {
        const values = {};
        new FormData(form).forEach((value, key) => {
            values[key] = value;
        });
        return values;
    }

    timeToMinutes(value) {
        if (!value) return null;
        const [hours, minutes] = value.split(":").map((part) => parseInt(part, 10));
        if (Number.isNaN(hours) || Number.isNaN(minutes)) return null;
        return hours * 60 + minutes;
    }

    minutesToTime(minutes) {
        if (minutes === null || minutes === undefined || minutes === "") return "";
        const total = parseInt(minutes, 10);
        if (Number.isNaN(total)) return "";
        const hours = Math.floor(total / 60) % 24;
        const mins = total % 60;
        return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
    }

    optionalFloat(value) {
        if (value === "" || value === null || value === undefined) return null;
        const parsed = parseFloat(value);
        return Number.isNaN(parsed) ? null : parsed;
    }

    async saveProvider() {
        const values = this.formValues(this.providerForm);
        const payload = {
            name: values.name,
            base_url: values.base_url,
            api_key: values.api_key,
            is_active: values.is_active === "1",
        };
        try {
            await Http.postJson("/admin/gateway/providers", payload);
            this.providerForm.reset();
            this.notify("Provider saved", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Save failed", "error");
        }
    }

    async saveModel() {
        const values = this.formValues(this.modelForm);
        const payload = {
            source_model: values.source_model,
            provider: values.provider,
            target_model: values.target_model,
            kind: values.kind,
            vision_provider: values.vision_provider,
            vision_model: values.vision_model,
            context_window: parseInt(values.context_window, 10) || 0,
            price_cache_hit_per_m: parseFloat(values.price_cache_hit_per_m) || 0,
            price_cache_miss_per_m: parseFloat(values.price_cache_miss_per_m) || 0,
            price_output_per_m: parseFloat(values.price_output_per_m) || 0,
            price_input_per_m: parseFloat(values.price_input_per_m) || 0,
            context_tier_threshold_tokens: parseInt(values.context_tier_threshold_tokens, 10) || 0,
            price_cache_hit_per_m_tier2: this.optionalFloat(values.price_cache_hit_per_m_tier2),
            price_cache_miss_per_m_tier2: this.optionalFloat(values.price_cache_miss_per_m_tier2),
            price_output_per_m_tier2: this.optionalFloat(values.price_output_per_m_tier2),
            price_input_per_m_tier2: this.optionalFloat(values.price_input_per_m_tier2),
            off_peak_start_minute: this.timeToMinutes(values.off_peak_start),
            off_peak_end_minute: this.timeToMinutes(values.off_peak_end),
            off_peak_discount_pct: parseFloat(values.off_peak_discount_pct) || 0,
            fallback_model: values.fallback_model || "",
            is_active: values.is_active === "1",
        };
        try {
            await Http.postJson("/admin/gateway/models", payload);
            this.modelForm.reset();
            this.notify("Model route saved", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Save failed", "error");
        }
    }

    onProviderClick(event) {
        const editName = event.target.dataset.editProvider;
        const delName = event.target.dataset.delProvider;
        if (editName) {
            const provider = this.providers.find((p) => p.name === editName);
            if (provider) this.fillProviderForm(provider);
        }
        if (delName) this.deleteProvider(delName);
    }

    fillProviderForm(provider) {
        const form = this.providerForm;
        form.name.value = provider.name;
        form.base_url.value = provider.base_url || "";
        form.api_key.value = provider.api_key || "";
        form.is_active.value = provider.is_active ? "1" : "0";
        form.name.scrollIntoView({ block: "center" });
    }

    async confirmAction(message, confirmLabel = "Delete") {
        if (window.app && window.app.dialog) {
            return window.app.dialog.confirm({ message, danger: true, confirmLabel });
        }
        return window.confirm(message);
    }

    async remove(url) {
        await Http.sendDelete(url, { silent: true });
    }

    async deleteProvider(name) {
        if (!(await this.confirmAction(`Delete provider "${name}"?`))) return;
        try {
            await this.remove(`/admin/gateway/providers/${encodeURIComponent(name)}`);
            this.notify("Provider deleted", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Delete failed", "error");
        }
    }

    onModelClick(event) {
        const editRaw = event.target.dataset.editModel;
        const delSource = event.target.dataset.delModel;
        if (editRaw) this.fillModelForm(JSON.parse(editRaw));
        if (delSource) this.deleteModel(delSource);
    }

    fillModelForm(model) {
        const form = this.modelForm;
        form.source_model.value = model.source_model || "";
        form.provider.value = model.provider || "";
        form.target_model.value = model.target_model || "";
        form.kind.value = model.kind || "chat";
        this.syncKindFields();
        form.vision_provider.value = model.vision_provider || "";
        form.vision_model.value = model.vision_model || "";
        form.context_window.value = model.context_window || 0;
        form.price_cache_hit_per_m.value = model.price_cache_hit_per_m || 0;
        form.price_cache_miss_per_m.value = model.price_cache_miss_per_m || 0;
        form.price_output_per_m.value = model.price_output_per_m || 0;
        form.price_input_per_m.value = model.price_input_per_m || 0;
        form.context_tier_threshold_tokens.value = model.context_tier_threshold_tokens || 0;
        form.price_cache_hit_per_m_tier2.value = model.price_cache_hit_per_m_tier2 ?? "";
        form.price_cache_miss_per_m_tier2.value = model.price_cache_miss_per_m_tier2 ?? "";
        form.price_output_per_m_tier2.value = model.price_output_per_m_tier2 ?? "";
        form.price_input_per_m_tier2.value = model.price_input_per_m_tier2 ?? "";
        form.off_peak_start.value = this.minutesToTime(model.off_peak_start_minute);
        form.off_peak_end.value = this.minutesToTime(model.off_peak_end_minute);
        form.off_peak_discount_pct.value = model.off_peak_discount_pct || 0;
        form.fallback_model.value = model.fallback_model || "";
        form.is_active.value = model.is_active ? "1" : "0";
        form.source_model.scrollIntoView({ block: "center" });
    }

    async deleteModel(source) {
        if (!(await this.confirmAction(`Delete model route "${source}"?`))) return;
        try {
            await this.remove(`/admin/gateway/models/${encodeURIComponent(source)}`);
            this.notify("Model route deleted", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Delete failed", "error");
        }
    }

    async loadQuotaRules() {
        const data = await Http.getJson("/admin/gateway/quota-rules");
        this.quotaRules = data.rules || [];
        this.renderQuotaDefaults(data.defaults || {});
        this.renderQuotaRules();
        return this.quotaRules.length;
    }

    renderQuotaDefaults(defaults) {
        const el = this.root.querySelector("#gw-quota-defaults");
        if (!el) return;
        const fmt = (v) => (Number(v) > 0 ? `$${Number(v).toFixed(2)}/24h` : "unlimited");
        el.innerHTML = `
            <strong>global defaults</strong> (from <a href="/admin/services">Services config</a>, apply per caller with no matching rule):
            member <code class="gw-code">${fmt(defaults.user)}</code>,
            admin <code class="gw-code">${fmt(defaults.admin)}</code>,
            guest <code class="gw-code">${fmt(defaults.guest)}</code>,
            internal <code class="gw-code">${fmt(defaults.internal)}</code>,
            access key <code class="gw-code">${fmt(defaults.key)}</code>`;
    }

    scopeLabel(rule) {
        const parts = [];
        parts.push(rule.owner_kind ? `role=${rule.owner_kind}` : "role=any");
        parts.push(rule.owner_id ? `user=${rule.owner_id}` : "user=any");
        parts.push(rule.app_reference ? `app=${rule.app_reference}` : "app=any");
        return parts.join(", ");
    }

    renderQuotaRules() {
        if (!this.quotaRules.length) {
            this.quotaRulesBody.innerHTML = `<tr><td colspan="6" class="admin-empty">No quota rules. Every caller is capped by the global defaults above.</td></tr>`;
            return;
        }
        this.quotaRulesBody.innerHTML = this.quotaRules
            .map((r) => {
                const limit = Number(r.limit_usd) > 0 ? `$${Number(r.limit_usd).toFixed(2)}` : "unlimited";
                const spent = `$${Number(r.spent_24h_usd || 0).toFixed(4)}`;
                return `<tr>
                    <td><code class="gw-code">${this.escape(this.scopeLabel(r))}</code></td>
                    <td>${limit}</td>
                    <td>${spent}</td>
                    <td>${r.is_active ? "yes" : "no"}</td>
                    <td>${this.escape(r.label || "")}</td>
                    <td class="gw-actions">
                        <button class="admin-btn admin-btn-sm" data-edit-quota='${this.attr(JSON.stringify(r))}'>Edit</button>
                        <button class="admin-btn admin-btn-sm" data-reset-quota='${this.attr(JSON.stringify(r))}'>Reset spend</button>
                        <button class="admin-btn admin-btn-sm admin-btn-danger" data-del-quota="${this.attr(r.uid)}">Delete</button>
                    </td>
                </tr>`;
            })
            .join("");
    }

    async saveQuotaRule() {
        const values = this.formValues(this.quotaForm);
        const payload = {
            owner_kind: values.owner_kind || null,
            owner_id: values.owner_id || null,
            app_reference: values.app_reference || null,
            limit_usd: parseFloat(values.limit_usd) || 0,
            is_active: values.is_active === "1",
            label: values.label || "",
        };
        if (values.uid) payload.uid = values.uid;
        try {
            await Http.postJson("/admin/gateway/quota-rules", payload);
            this.resetQuotaForm();
            this.notify("Quota rule saved", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Save failed", "error");
        }
    }

    fillQuotaForm(rule) {
        const form = this.quotaForm;
        form.uid.value = rule.uid || "";
        form.owner_kind.value = rule.owner_kind || "";
        form.owner_id.value = rule.owner_id || "";
        form.app_reference.value = rule.app_reference || "";
        form.limit_usd.value = rule.limit_usd || 0;
        form.is_active.value = rule.is_active ? "1" : "0";
        form.label.value = rule.label || "";
        this.quotaCancelEdit.hidden = false;
        form.owner_kind.scrollIntoView({ block: "center" });
    }

    resetQuotaForm() {
        this.quotaForm.reset();
        this.quotaForm.uid.value = "";
        this.quotaCancelEdit.hidden = true;
    }

    onQuotaRuleClick(event) {
        const editRaw = event.target.dataset.editQuota;
        const resetRaw = event.target.dataset.resetQuota;
        const delUid = event.target.dataset.delQuota;
        if (editRaw) this.fillQuotaForm(JSON.parse(editRaw));
        if (resetRaw) this.resetQuotaSpend(JSON.parse(resetRaw));
        if (delUid) this.deleteQuotaRule(delUid);
    }

    async resetQuotaSpend(rule) {
        const label = this.scopeLabel(rule);
        if (!(await this.confirmAction(`Reset the counted 24h spend for ${label}? The usage history is kept.`, "Reset"))) return;
        try {
            await Http.postJson("/admin/gateway/quota-resets", {
                owner_kind: rule.owner_kind || "",
                owner_id: rule.owner_id || "",
                app_reference: rule.app_reference || "",
            });
            this.notify("Quota spend reset", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Reset failed", "error");
        }
    }

    async deleteQuotaRule(uid) {
        if (!(await this.confirmAction("Delete this quota rule?"))) return;
        try {
            await this.remove(`/admin/gateway/quota-rules/${encodeURIComponent(uid)}`);
            this.notify("Quota rule deleted", "success");
            await this.reload();
        } catch (err) {
            this.notify(err.message || "Delete failed", "error");
        }
    }
}
