/**
 * reading.e2e — coverage for src/tools/reading.ts.
 *
 * Asserts that the read-path tools see what ImapFixtures seeds. Uses
 * get_email_by_id (single-UID FETCH) where possible to sidestep the
 * mailbox-EXISTS cache lag Greenmail can show for get_emails right after
 * APPENDs from a different connection.
 */

import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { startE2E, type E2EHarness } from "../mcp-client.js";
import * as docker from "../support/docker.js";
import {
  NEWSLETTER_TOKEN_DISPATCH,
  PROMO_BATCH,
  PROMO_CREDIT_KARMA,
  RELEASE_NVIDIA,
  WORK_THREAD_REPLY,
  WORK_THREAD_ROOT,
} from "../fixtures/seed-data.js";

describe("reading.e2e", () => {
  let h: E2EHarness;

  beforeAll(async () => {
    await docker.restart();
    h = await startE2E();
  }, 60_000);

  afterAll(async () => {
    if (h) {
      try { await h.imap.wipe(); } catch { /* ignore */ }
      await h.close();
    }
  });

  beforeEach(async () => {
    await h.resetState();
  });

  describe("get_email_by_id", () => {
    it("returns subject + from for a seeded INBOX UID", async () => {
      const uid = await h.imap.appendSeed("INBOX", PROMO_CREDIT_KARMA);
      await h.call("clear_cache");
      const result = h.json<{ subject: string; from: string }>(
        await h.call("get_email_by_id", { emailId: String(uid), folder: "INBOX" })
      );
      expect(result.subject).toBe(PROMO_CREDIT_KARMA.subject);
      expect(result.from).toContain("creditkarma");
    });

    it("returns an error for a UID that doesn't exist", async () => {
      const raw = await h.callRaw("get_email_by_id", { emailId: "999999", folder: "INBOX" });
      // Either MCP-level error or domain isError:true.
      const ok = "ok" in raw && raw.ok && raw.isError !== true;
      expect(ok).toBe(false);
    });
  });

  describe("get_emails", () => {
    // mailpouch's mailbox-EXISTS cache lags side-channel APPENDs on Greenmail.
    // get_email_by_id (above) reads correctly because it does a UID FETCH that
    // doesn't depend on the SELECT-cached EXISTS counter. Bridge handles
    // EXPUNGE/EXISTS notifications correctly via IDLE — covered Phase 2.
    it.skip("lists messages from INBOX after a fresh sync — bridge-only", async () => {
      for (const seed of PROMO_BATCH) await h.imap.appendSeed("INBOX", seed);
      await h.call("clear_cache");
      await h.call("sync_emails", { folder: "INBOX", limit: 20 });
      const result = h.json<{ emails: { subject: string }[] }>(
        await h.call("get_emails", { folder: "INBOX", limit: 20 })
      );
      const subjects = new Set(result.emails.map((e) => e.subject));
      expect(subjects.has(PROMO_CREDIT_KARMA.subject)).toBe(true);
      expect(subjects.has(RELEASE_NVIDIA.subject)).toBe(true);
    });

    it("respects the limit parameter", async () => {
      for (const seed of PROMO_BATCH) await h.imap.appendSeed("INBOX", seed);
      await h.call("clear_cache");
      await h.call("sync_emails", { folder: "INBOX", limit: 20 });
      const result = h.json<{ emails: unknown[] }>(
        await h.call("get_emails", { folder: "INBOX", limit: 2 })
      );
      expect(result.emails.length).toBeLessThanOrEqual(2);
    });

    it("returns empty for a folder that exists but has no messages", async () => {
      await h.imap.createMailbox("Folders/Empty");
      const result = h.json<{ emails: unknown[] }>(
        await h.call("get_emails", { folder: "Folders/Empty", limit: 10 })
      );
      expect(result.emails.length).toBe(0);
    });

    it("returns an actionable not-found error for a missing folder (Cluster 6)", async () => {
      const raw = await h.callRaw("get_emails", { folder: "Folders/DoesNotExist", limit: 10 });
      const text = "message" in raw ? raw.message : raw.content?.[0]?.text ?? "";
      // Must name the folder and not collapse to the opaque generic string.
      expect(text).toContain("Folders/DoesNotExist");
      expect(text.toLowerCase()).toContain("not found");
      expect(text).not.toBe("An error occurred");
    });
  });

  describe("get_thread", () => {
    // get_thread internally calls searchEmails across INBOX + Sent — same
    // mailbox-EXISTS cache lag as get_emails above.
    it.skip("groups a reply with its root by In-Reply-To — bridge-only", async () => {
      const rootUid = await h.imap.appendSeed("INBOX", WORK_THREAD_ROOT);
      await h.imap.appendSeed("INBOX", WORK_THREAD_REPLY);
      await h.call("clear_cache");
      await h.call("sync_emails", { folder: "INBOX", limit: 20 });
      const result = h.json<{ emails: { subject: string }[] }>(
        await h.call("get_thread", { email_id: String(rootUid), folder: "INBOX" })
      );
      const subjects = result.emails.map((e) => e.subject);
      expect(subjects.some((s) => s.includes("Q2 planning"))).toBe(true);
    });
  });

  describe("get_unread_count", () => {
    it("reports the unread count across folders", async () => {
      await h.imap.appendSeed("INBOX", PROMO_CREDIT_KARMA);
      await h.imap.appendSeed("INBOX", NEWSLETTER_TOKEN_DISPATCH, ["\\Seen"]);
      await h.call("clear_cache");
      const result = h.json<{ unreadByFolder: Record<string, number>; totalUnread: number }>(
        await h.call("get_unread_count")
      );
      expect(typeof result.totalUnread).toBe("number");
      expect(result.unreadByFolder).toBeTypeOf("object");
    });
  });

  describe("sync_emails", () => {
    it("returns success with a folder + count", async () => {
      await h.imap.appendSeed("INBOX", PROMO_CREDIT_KARMA);
      const result = h.json<{ success: boolean; folder: string; count: number }>(
        await h.call("sync_emails", { folder: "INBOX", limit: 10 })
      );
      expect(result.success).toBe(true);
      expect(result.folder).toBe("INBOX");
      expect(result.count).toBeGreaterThanOrEqual(0);
    });

    it("returns an actionable not-found error for a missing folder (Cluster 6)", async () => {
      const raw = await h.callRaw("sync_emails", { folder: "Folders/Nope", limit: 10 });
      const text = "message" in raw ? raw.message : raw.content?.[0]?.text ?? "";
      expect(text).toContain("Folders/Nope");
      expect(text.toLowerCase()).toContain("not found");
      expect(text).not.toBe("An error occurred");
    });
  });
});
