import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
	getMetricsSystemStats,
	getMetricsText,
	getSystemPerformanceDashboard,
	initMetricsCentral,
	queryMetrics,
	recordCost,
	recordCounter,
	recordDatabaseOperation,
	recordGauge,
	recordHistogram,
	recordModelRequest,
	recordToolExecution,
	registerMetricMeta,
	stopMetricsCentral,
	updateResourceGauges,
} from "../metrics-central.js";

describe("metrics-central", () => {
	beforeEach(() => {
		initMetricsCentral("/tmp/test-project");
	});

	afterEach(() => {
		stopMetricsCentral();
	});

	it("recordCounter_increments_and_exposes", () => {
		recordCounter("sf_test_counter", { label: "a" }, 3);
		recordCounter("sf_test_counter", { label: "a" }, 2);
		const text = getMetricsText();
		expect(text).toContain('sf_test_counter{label="a"} 5');
		expect(text).toContain("# TYPE sf_test_counter counter");
	});

	it("recordGauge_sets_and_exposes", () => {
		recordGauge("sf_test_gauge", 42, { env: "prod" });
		const text = getMetricsText();
		expect(text).toContain('sf_test_gauge{env="prod"} 42');
		expect(text).toContain("# TYPE sf_test_gauge gauge");
	});

	it("recordHistogram_observes_and_exposes_buckets", () => {
		registerMetricMeta("sf_test_hist", "Test histogram", [], [1, 5, 10]);
		recordHistogram("sf_test_hist", 3);
		recordHistogram("sf_test_hist", 7);
		const text = getMetricsText();
		expect(text).toContain('sf_test_hist_bucket{le="1"} 0');
		expect(text).toContain('sf_test_hist_bucket{le="5"} 1');
		expect(text).toContain('sf_test_hist_bucket{le="10"} 2');
		expect(text).toContain("sf_test_hist_count 2");
		expect(text).toContain("sf_test_hist_sum 10");
	});

	it("subagent_metrics_tracked", () => {
		recordCounter("sf_subagent_dispatch_total", {
			work_mode: "build",
			permission_profile: "trusted",
		});
		recordCounter("sf_subagent_dispatch_blocked", {
			reason: "provider",
			work_mode: "build",
			permission_profile: "trusted",
		});
		const text = getMetricsText();
		expect(text).toContain(
			'sf_subagent_dispatch_total{permission_profile="trusted",work_mode="build"} 1',
		);
		expect(text).toContain(
			'sf_subagent_dispatch_blocked{permission_profile="trusted",reason="provider",work_mode="build"} 1',
		);
	});

	it("mode_transition_metrics_tracked", () => {
		recordCounter("sf_mode_transition_total", {
			axis: "work_mode",
			from: "chat",
			to: "build",
			reason: "user_command",
		});
		const text = getMetricsText();
		expect(text).toContain(
			'sf_mode_transition_total{axis="work_mode",from="chat",reason="user_command",to="build"} 1',
		);
	});

	it("session_id_auto_injected", () => {
		initMetricsCentral("/tmp/test-project", { sessionId: "sess-abc-123" });
		recordCounter("sf_test_session", { label: "x" });
		const text = getMetricsText();
		expect(text).toContain('session_id="sess-abc-123"');
	});

	it("cost_metrics_tracked", () => {
		recordCost("unit-42", "claude-sonnet-4", 1500, 800, 0.045, "build");
		const text = getMetricsText();
		expect(text).toContain(
			'sf_cost_total{model_id="claude-sonnet-4",unit_id="unit-42",work_mode="build"} 0.045',
		);
		expect(text).toContain(
			'sf_tokens_input_total{model_id="claude-sonnet-4"} 1500',
		);
		expect(text).toContain(
			'sf_tokens_output_total{model_id="claude-sonnet-4"} 800',
		);
		expect(text).toContain(
			'sf_cost_last{model_id="claude-sonnet-4",unit_id="unit-42"} 0.045',
		);
	});

	it("invalid_metric_name_rejected", () => {
		expect(() => recordCounter("bad name with spaces", {})).toThrow();
		expect(() => recordCounter("123_starts_with_number", {})).toThrow();
		expect(() => recordCounter("", {})).toThrow();
	});

	it("label_escaping_handles_special_chars", () => {
		recordCounter("sf_test_escape", { key: "a=b,c" });
		const text = getMetricsText();
		expect(text).toContain('key="a=b,c"');
	});

	it("queryMetrics_returns_empty_without_db", () => {
		const results = queryMetrics(null, "sess-1", "sf_test");
		expect(results).toEqual([]);
	});

	it("dashboard_reads_counter_gauge_and_histogram_values", () => {
		recordCost("unit-dashboard", "model-dashboard", 10, 20, 0.5, "build");
		recordToolExecution("read", 30);
		recordModelRequest("model-dashboard", 40);
		recordDatabaseOperation("select", 50);
		updateResourceGauges({
			activeSessions: 1,
			activeAgents: 2,
			concurrentToolCalls: 3,
		});

		const dashboard = getSystemPerformanceDashboard();
		expect(dashboard.cost).toBeGreaterThanOrEqual(0.5);
		expect(dashboard.tokens.input).toBeGreaterThanOrEqual(10);
		expect(dashboard.tokens.output).toBeGreaterThanOrEqual(20);
		expect(dashboard.performance.averageToolExecution).toBeGreaterThanOrEqual(
			30,
		);
		expect(dashboard.performance.averageModelRequest).toBeGreaterThanOrEqual(
			40,
		);
		expect(dashboard.performance.averageDatabaseQuery).toBeGreaterThanOrEqual(
			50,
		);
		expect(dashboard.resources.activeSessions).toBe(1);
		expect(dashboard.resources.activeAgents).toBe(2);
		expect(dashboard.resources.concurrentToolCalls).toBe(3);
		expect(getMetricsSystemStats().databaseStatus).toBe("connected");
	});

	it("stopMetricsCentral_persists_metrics_to_db", async () => {
		const { DatabaseSync } = await import("node:sqlite");

		// The beforeEach already called initMetricsCentral("/tmp/test-project"),
		// so we record in the already-open metrics.db and verify after stop.
		recordCounter("sf_test_db_counter", { label: "a=b,c" }, 2);
		stopMetricsCentral(); // flush + close; afterEach stopMetricsCentral is a no-op

		const dbPath = "/tmp/test-project/.sf/metrics.db";
		const db = new DatabaseSync(dbPath, { open: true });
		const rows = db
			.prepare(
				"SELECT name, type, value FROM metrics WHERE name = 'sf_test_db_counter' ORDER BY id DESC LIMIT 10",
			)
			.all();
		db.close();

		expect(rows).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: "sf_test_db_counter",
					type: "counter",
					value: 2,
				}),
			]),
		);
	});
});
