import { describe, expect, it } from "vitest";
import { checkCleanupWarnings, computeDaysUntilCleanup } from "../src/cleanup-warning.js";
import type { SessionInfo } from "../src/types.js";

function makeSession(overrides: Partial<SessionInfo>): SessionInfo {
  return {
    provider: "claude-code",
    sessionId: "test-session-id",
    slug: "test-slug",
    project: "~/Code/test",
    cwd: "/Users/test/Code/test",
    version: "1.0.0",
    timestamp: new Date().toISOString(),
    lineCount: 100,
    fileSize: 1024,
    filePath: "/path/to/session.jsonl",
    filePaths: ["/path/to/session.jsonl"],
    firstPrompt: "test prompt",
    ...overrides,
  };
}

describe("computeDaysUntilCleanup", () => {
  const NOW = Date.parse("2026-04-25T12:00:00.000Z");

  it("returns undefined when cleanupPeriodDays is 0 (disabled)", () => {
    expect(computeDaysUntilCleanup(new Date().toISOString(), 0)).toBeUndefined();
  });

  it("returns undefined for invalid timestamp", () => {
    expect(computeDaysUntilCleanup("invalid-date", 30)).toBeUndefined();
  });

  it("returns correct days for a recent session", () => {
    // Session created 5 days ago with 30-day cleanup → 25 days left (floor)
    const fiveDaysAgo = new Date(NOW - 5 * 24 * 60 * 60 * 1000).toISOString();
    const daysLeft = computeDaysUntilCleanup(fiveDaysAgo, 30, NOW);
    expect(daysLeft).toBe(25);
  });

  it("returns 0 for sessions past cleanup deadline", () => {
    const fortyDaysAgo = new Date(NOW - 40 * 24 * 60 * 60 * 1000).toISOString();
    const daysLeft = computeDaysUntilCleanup(fortyDaysAgo, 30, NOW);
    expect(daysLeft).toBe(0);
  });

  it("returns correct days for session near expiry", () => {
    // Session created 27 days ago with 30-day cleanup → 3 days left
    const twentySevenDaysAgo = new Date(NOW - 27 * 24 * 60 * 60 * 1000).toISOString();
    const daysLeft = computeDaysUntilCleanup(twentySevenDaysAgo, 30, NOW);
    expect(daysLeft).toBe(3);
  });

  it("uses Math.floor for conservative estimate", () => {
    // Session 25.9 days old → 30 - 25.9 = 4.1 → floor = 4 (not ceil = 5)
    const almostTwentySixDays = new Date(NOW - 25.9 * 24 * 60 * 60 * 1000).toISOString();
    const daysLeft = computeDaysUntilCleanup(almostTwentySixDays, 30, NOW);
    expect(daysLeft).toBe(4);
  });
});

describe("checkCleanupWarnings", () => {
  it("returns null when no sessions are expiring", () => {
    const sessions = [
      makeSession({ timestamp: new Date().toISOString() }),
      makeSession({ timestamp: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString() }),
    ];
    expect(checkCleanupWarnings(sessions, 30)).toBeNull();
  });

  it("returns null when cleanupPeriodDays is 0", () => {
    const sessions = [
      makeSession({ timestamp: new Date(Date.now() - 100 * 24 * 60 * 60 * 1000).toISOString() }),
    ];
    expect(checkCleanupWarnings(sessions, 0)).toBeNull();
  });

  it("detects sessions within warning threshold", () => {
    const sessions = [
      // 24.5 days old → 5 days left → within 7-day threshold
      // (use .5 to avoid Math.floor boundary flakiness across timezones)
      makeSession({
        timestamp: new Date(Date.now() - 24.5 * 24 * 60 * 60 * 1000).toISOString(),
        filePath: "/path/expiring.jsonl",
      }),
      // 10 days old → 20 days left → safe
      makeSession({
        timestamp: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
        filePath: "/path/safe.jsonl",
      }),
    ];
    const result = checkCleanupWarnings(sessions, 30);
    expect(result).not.toBeNull();
    expect(result!.expiringCount).toBe(1);
    expect(result!.soonestDays).toBe(5);
    expect(result!.cleanupPeriodDays).toBe(30);
  });

  it("ignores non-claude-code sessions", () => {
    const sessions = [
      makeSession({
        provider: "cursor",
        timestamp: new Date(Date.now() - 29 * 24 * 60 * 60 * 1000).toISOString(),
      }),
    ];
    expect(checkCleanupWarnings(sessions, 30)).toBeNull();
  });

  it("reports the soonest expiry correctly", () => {
    const sessions = [
      makeSession({
        timestamp: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000).toISOString(),
        filePath: "/path/a.jsonl",
      }),
      makeSession({
        timestamp: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString(),
        filePath: "/path/b.jsonl",
      }),
      makeSession({
        // subtract 1 minute buffer to avoid Math.floor boundary flake from ms drift between Date.now() calls
        timestamp: new Date(Date.now() - (29 * 24 * 60 * 60 * 1000 - 60_000)).toISOString(),
        filePath: "/path/c.jsonl",
      }),
    ];
    const result = checkCleanupWarnings(sessions, 30);
    expect(result).not.toBeNull();
    expect(result!.expiringCount).toBe(3);
    expect(result!.soonestDays).toBe(1);
  });

  it("supports custom warning threshold", () => {
    const sessions = [
      // 25 days old → 5 days left → outside 3-day threshold
      makeSession({
        timestamp: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000).toISOString(),
      }),
    ];
    expect(checkCleanupWarnings(sessions, 30, 3)).toBeNull();

    // 28 days old → 2 days left → within 3-day threshold
    const urgentSessions = [
      makeSession({
        timestamp: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString(),
      }),
    ];
    const result = checkCleanupWarnings(urgentSessions, 30, 3);
    expect(result).not.toBeNull();
    expect(result!.expiringCount).toBe(1);
  });
});
