import { nextTick, ref } from "vue";
import { describe, expect, it } from "vitest";
import { createSessionRequestBudgetPersistence } from "../src/dimsdk/core/store/request-budget.ts";
import { createSessionUsagePersistence } from "../src/dimsdk/core/store/session-usage.ts";
import { createDimSdkState } from "../src/dimsdk/core/store/state.ts";
import { useChatStatus } from "../src/dimsdk/pages/ChatPage/logic/status.ts";

describe("dimsdk chat status reactivity", () => {
  it("updates visible usage stats when session usage changes", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    const { applyUsageFromSession } = createSessionUsagePersistence({ state });
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    expect(status.headerStatsDisplay.value).toContain("0 tok");

    applyUsageFromSession({
      promptTokens: 1260,
      completionTokens: 71,
      totalTokens: 1331,
    });
    await nextTick();

    expect(state.usage.total_tokens).toBe(1331);
    expect(status.headerStatsDisplay.value).toContain("1,331 tok");
  });

  it("clamps visible context usage to 100 percent when the request budget exceeds the window", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 2400;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 9720,
      inputBudgetTokens: 2400,
      thresholdTokens: 2400,
      maxInputTokens: 2400,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBe(100);
    expect(status.ctxPctChip.value).toContain("100%");
  });

  it("uses the configured context window when the live request input budget is smaller", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 32000;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 3172,
      inputBudgetTokens: 1920,
      thresholdTokens: 1920,
      maxInputTokens: 1920,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBe(10);
    expect(status.ctxPctChip.value).toContain("10%");
  });

  it("uses the configured context window when the live request input budget is larger", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 4000;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 1331,
      inputBudgetTokens: 6400,
      thresholdTokens: 6400,
      maxInputTokens: 6400,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBe(33);
    expect(status.ctxPctChip.value).toContain("33%");
  });

  it("keeps using the configured context window even when model metadata differs", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.provider.activeId = "openai";
    state.config.model = "deepseek-v3.1";
    state.config.context_window = 32000;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 1331,
      inputBudgetTokens: 4000,
      thresholdTokens: 4000,
      maxInputTokens: 4000,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBe(4);
    expect(status.ctxPctChip.value).toContain("4%");
  });

  it("does not infer the context denominator when config context window is unset", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.provider.activeId = "openai";
    state.config.model = "deepseek-v3.1";
    state.config.context_window = 0;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 1331,
      inputBudgetTokens: 4000,
      thresholdTokens: 4000,
      maxInputTokens: 4000,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBeNull();
    expect(status.ctxPctChip.value).toBe("--");
  });

  it("does not fall back to cumulative session totals when request budget is unavailable", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 8000;
    state.usage.total_tokens = 6990;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    await nextTick();

    expect(status.ctxPct.value).toBeNull();
    expect(status.ctxPctChip.value).toBe("--");
  });

  it("uses the estimated request input tokens when the budget outcome is unknown", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 8000;
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    state.lastRequestBudget = {
      autoBudgetOutcome: 'budget-unknown',
      estimatedInputTokens: 1600,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    } as any;
    await nextTick();

    expect(status.ctxPct.value).toBe(20);
    expect(status.ctxPctChip.value).toContain("20%");
  });

  it("keeps the last non-zero context percentage when a later budget update reports zero input tokens", async () => {
    const { state } = createDimSdkState({
      context: { cwd: process.cwd(), env: {} } as any,
      agent: { getMode: () => "agent" } as any,
    });
    state.config.context_window = 32000;
    const { applyLastRequestBudget } = createSessionRequestBudgetPersistence({
      state,
    });
    const status = useChatStatus({
      store: { state } as any,
      ui: {
        style: (_tokens?: unknown, extra?: Record<string, unknown>) => extra ?? {},
        token: () => ({ fg: "whiteBright", bg: "black" }),
        capability: { level: 8 },
      } as any,
      uiMode: ref("agent") as any,
    });

    applyLastRequestBudget({
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 6400,
      inputBudgetTokens: 32000,
      thresholdTokens: 32000,
      maxInputTokens: 32000,
      triggerRatio: 1,
      updatedAtMs: Date.now(),
      usedAutoCompaction: false,
    });
    await nextTick();

    expect(status.ctxPct.value).toBe(20);

    applyLastRequestBudget({
      autoBudgetOutcome: 'not-needed',
      estimatedInputTokens: 0,
      inputBudgetTokens: 32000,
      thresholdTokens: 32000,
      maxInputTokens: 32000,
      triggerRatio: 1,
      updatedAtMs: Date.now() + 1,
      usedAutoCompaction: false,
    });
    await nextTick();

    expect(status.ctxPct.value).toBe(20);
    expect(status.ctxPctChip.value).toContain("20%");
  });
});
