// retoor <retoor@molodetz.nl>

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

class AiUsageMonitor {
    constructor() {
        this.root = document.getElementById("ai-usage-root");
        this.windowSelect = document.getElementById("ai-usage-window");
        this.dividedToggle = document.getElementById("ai-usage-divided");
        this.generated = document.querySelector("[data-meta='generated']");
        this.pollMs = 15000;
        this.hours = 48;
    }

    start() {
        if (!this.root) return;
        if (this.windowSelect) {
            this.windowSelect.addEventListener("change", () => {
                const parsed = parseInt(this.windowSelect.value, 10);
                this.hours = Number.isNaN(parsed) ? 48 : parsed;
                this.subscribe();
                this.poll();
            });
        }
        if (this.dividedToggle) {
            this.dividedToggle.addEventListener("change", () => {
                this.root.classList.toggle("show-divided", this.dividedToggle.checked);
            });
        }
        this.subscribe();
        this.poller = new Poller(() => this.poll(), 60000, { pauseHidden: true });
    }

    subscribe() {
        const pubsub = window.app && window.app.pubsub;
        if (!pubsub) return;
        if (this.unsubscribe) this.unsubscribe();
        this.unsubscribe = pubsub.subscribe(`admin.ai-usage.${this.hours}`, (data) => this.render(data));
    }

    async poll() {
        try {
            const data = await Http.getJson(`/admin/ai-usage/data?hours=${this.hours}`);
            this.render(data);
        } catch {
        }
    }

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

    money(value) {
        const number = Number(value || 0);
        return number < 1 ? `$${number.toFixed(6)}` : `$${number.toFixed(2)}`;
    }

    integer(value) {
        return Number(value || 0).toLocaleString("en-GB");
    }

    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;
    }

    table(columns, rows, className) {
        const node = this.el("table", className || "metric-table");
        const thead = this.el("thead");
        const headRow = this.el("tr");
        for (const col of columns) headRow.appendChild(this.el("th", null, col));
        thead.appendChild(headRow);
        node.appendChild(thead);
        const tbody = this.el("tbody");
        for (const row of rows) {
            const tr = this.el("tr");
            for (const cell of row) tr.appendChild(this.el("td", null, cell));
            tbody.appendChild(tr);
        }
        node.appendChild(tbody);
        return node;
    }

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

    psetTable(title, pset) {
        const rows = [
            ["avg", this.integer(pset.avg)],
            ["p50", this.integer(pset.p50)],
            ["p90", this.integer(pset.p90)],
            ["p95", this.integer(pset.p95)],
            ["p99", this.integer(pset.p99)],
            ["max", this.integer(pset.max)],
        ];
        const node = this.table([title, "value"], rows, "metric-table pset-table");
        return node;
    }

    psetGrid(title, sets) {
        const grid = this.el("div", "ai-usage-grid");
        for (const [label, pset] of sets) {
            const block = this.el("div");
            block.appendChild(this.el("h3", null, label));
            block.appendChild(this.psetTable("stat", pset));
            grid.appendChild(block);
        }
        return this.section(title, grid);
    }

    groupRows(items) {
        return (items || []).map((item) => [
            item.key, this.integer(item.requests), this.integer(item.total_tokens), this.money(item.cost_usd),
        ]);
    }

    render(data) {
        this.root.textContent = "";
        if (this.generated && data.generated_at) {
            const windowLabel = data.window_hours ? `${data.window_hours}h` : "all time";
            this.generated.textContent = `Window: ${windowLabel}  -  updated ${DateFormat.format(data.generated_at, true)}`;
        }
        if (!data.requests) {
            this.root.appendChild(this.el("p", "admin-empty", "No AI gateway traffic recorded in this window yet."));
            return;
        }
        const v = data.volume || {};
        const t = data.tokens || {};
        const l = data.latency || {};
        const e = data.errors || {};
        const c = data.cost || {};
        const b = data.behavior || {};
        const conc = v.concurrency || {};

        this.root.appendChild(this.section("Overview", this.cards([
            { label: "Requests", value: this.integer(v.requests) },
            { label: "Success", value: `${v.requests ? (100 * v.success / v.requests).toFixed(1) : 0}%` },
            { label: "Error rate", value: `${e.error_rate_pct}%` },
            { label: "Requests / hour", value: this.integer(v.requests_per_hour) },
            { label: "Peak req / min", value: this.integer(v.peak_requests_per_minute) },
            { label: "Cost (window)", value: this.money(c.total_usd) },
            { label: "Cost this hour", value: this.money(c.this_hour_usd) },
            { label: "Cost 24h", value: this.money(c.last_24h_usd) },
            { label: "Projected / month", value: this.money(c.projected_monthly_usd) },
            { label: "Tokens total", value: this.integer(t.total) },
            { label: "Upstream avail.", value: `${l.upstream_availability_pct}%` },
            { label: "Concurrency peak", value: this.integer(conc.peak) },
        ])));

        this.root.appendChild(this.section("Tokens", this.cards([
            { label: "Prompt total", value: this.integer(t.prompt_total) },
            { label: "Completion total", value: this.integer(t.completion_total) },
            { label: "Total", value: this.integer(t.total) },
            { label: "Cache hit rate", value: `${((t.cache_hit_rate || 0) * 100).toFixed(1)}%` },
            { label: "Input:output", value: t.input_output_ratio },
            { label: "Cache hit tokens", value: this.integer(t.cache_hit_total) },
            { label: "Cache miss tokens", value: this.integer(t.cache_miss_total) },
            { label: "Reasoning tokens", value: this.integer(t.reasoning_total) },
            { label: "Tokens / hour", value: this.integer(t.tokens_per_hour) },
            { label: "Context util avg", value: t.context_utilization_avg === null || t.context_utilization_avg === undefined ? "n/a" : `${(t.context_utilization_avg * 100).toFixed(1)}%` },
        ])));
        this.root.appendChild(this.psetGrid("Token distribution per request", [
            ["Prompt tokens", t.prompt || {}],
            ["Completion tokens", t.completion || {}],
            ["Total tokens", t.total_per_request || {}],
        ]));

        this.root.appendChild(this.section("Latency and throughput", this.cards([
            { label: "Upstream availability", value: `${l.upstream_availability_pct}%` },
            { label: "Gateway overhead share", value: `${((l.gateway_overhead_share || 0) * 100).toFixed(1)}%` },
            { label: "TTFT", value: "n/a (non-streaming)" },
            { label: "Inter-token", value: "n/a (non-streaming)" },
        ])));
        this.root.appendChild(this.psetGrid("Latency percentiles (ms)", [
            ["Upstream round-trip", l.upstream_ms || {}],
            ["Gateway overhead", l.gateway_overhead_ms || {}],
            ["Queue wait", l.queue_wait_ms || {}],
            ["Connection establish", l.connect_ms || {}],
            ["Total", l.total_ms || {}],
            ["Tokens / second", l.tokens_per_second || {}],
        ]));

        this.root.appendChild(this.section("Errors and reliability", this.cards([
            { label: "Total errors", value: this.integer(e.total) },
            { label: "Error rate", value: `${e.error_rate_pct}%` },
            { label: "4xx", value: this.integer(e.count_4xx) },
            { label: "5xx", value: this.integer(e.count_5xx) },
            { label: "Timeouts", value: this.integer(e.timeouts) },
            { label: "Retries attempted", value: this.integer(e.retries_attempted) },
            { label: "Retries succeeded", value: this.integer(e.retries_succeeded) },
            { label: "Circuit opens", value: this.integer(e.circuit_open_events) },
        ]), this.table(["Error category", "Count"],
            Object.entries(e.by_category || {}).map(([k, n]) => [k, this.integer(n)]))));

        this.root.appendChild(this.section("Cost", this.cards([
            { label: "Total", value: this.money(c.total_usd) },
            { label: "Input cost", value: this.money(c.input_usd) },
            { label: "Output cost", value: this.money(c.output_usd) },
            { label: "Per 1k tokens", value: this.money(c.effective_per_1k_tokens_usd) },
            { label: "Caching savings", value: this.money(c.caching_savings_usd) },
            { label: "This hour", value: this.money(c.this_hour_usd) },
            { label: "Last 24h", value: this.money(c.last_24h_usd) },
            { label: "Projected / month", value: this.money(c.projected_monthly_usd) },
        ])));

        this.root.appendChild(this.section("Behavior", this.cards([
            { label: "Unique callers", value: this.integer(b.unique_callers) },
            { label: "Req / caller avg", value: this.integer((b.requests_per_caller || {}).avg) },
            { label: "Req / caller p95", value: this.integer((b.requests_per_caller || {}).p95) },
            { label: "Heaviest caller", value: this.integer((b.requests_per_caller || {}).max) },
            { label: "Avg temperature", value: b.avg_temperature === null || b.avg_temperature === undefined ? "n/a" : b.avg_temperature },
            { label: "Avg top_p", value: b.avg_top_p === null || b.avg_top_p === undefined ? "n/a" : b.avg_top_p },
            { label: "Tool calls", value: this.integer(b.tool_call_requests) },
            { label: "Streaming", value: this.integer(b.streaming_requests) },
            { label: "Non-streaming", value: this.integer(b.non_streaming_requests) },
        ])));

        const divided = this.el("div", "ai-divided");
        divided.appendChild(this.section("By model",
            this.table(["Model", "Requests", "Tokens", "Cost"], this.groupRows(v.by_model))));
        divided.appendChild(this.section("By endpoint",
            this.table(["Endpoint", "Requests", "Tokens", "Cost"], this.groupRows(v.by_endpoint))));
        divided.appendChild(this.section("By backend",
            this.table(["Backend", "Requests", "Tokens", "Cost"], this.groupRows(v.by_backend))));
        divided.appendChild(this.section("By caller",
            this.table(["Caller", "Requests", "Tokens", "Cost"], this.groupRows(v.by_caller))));
        divided.appendChild(this.section("User agents",
            this.table(["User agent", "Requests"],
                (b.user_agents || []).map((item) => [item.key, this.integer(item.requests)]))));
        this.root.appendChild(divided);

        const hourlyRows = (data.hourly || []).map((h) => {
            const [day, hour] = String(h.hour).split("T");
            return [
            `${DateFormat.format(day)} ${(hour || "00").padStart(2, "0")}:00`,
            this.integer(h.requests),
            this.integer(h.success),
            this.integer(h.failed),
            this.integer(h.prompt_tokens),
            this.integer(h.completion_tokens),
            this.integer(h.total_tokens),
            this.money(h.cost_usd),
            this.integer(h.avg_latency_ms),
            this.integer(h.active_owners),
            this.integer(h.new_owners),
        ];
        });
        this.root.appendChild(this.section("Hourly breakdown",
            this.table(["Hour", "Requests", "OK", "Failed", "Prompt", "Completion", "Total", "Cost", "Avg ms", "Callers", "New"], hourlyRows)));
    }
}

window.AiUsageMonitor = AiUsageMonitor;
