import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { resetEmailProvider, getEmailProvider, sendEmail } from "./provider";

const ORIG_ENV = { ...process.env };

beforeEach(() => {
  resetEmailProvider();
  process.env = { ...ORIG_ENV };
});

afterEach(() => {
  resetEmailProvider();
  process.env = { ...ORIG_ENV };
});

describe("email provider resolution", () => {
  it("uses MockProvider when RESEND_API_KEY is missing", () => {
    delete process.env.RESEND_API_KEY;
    expect(getEmailProvider().name).toBe("mock");
  });

  it("uses MockProvider when RESEND_API_KEY is empty string", () => {
    process.env.RESEND_API_KEY = "";
    expect(getEmailProvider().name).toBe("mock");
  });

  it("uses MockProvider when RESEND_API_KEY is whitespace", () => {
    process.env.RESEND_API_KEY = "   ";
    expect(getEmailProvider().name).toBe("mock");
  });

  it("uses ResendProvider when RESEND_API_KEY is set", () => {
    process.env.RESEND_API_KEY = "re_test_key";
    expect(getEmailProvider().name).toBe("resend");
  });
});

describe("MockProvider.send", () => {
  it("returns ok with a generated id", async () => {
    delete process.env.RESEND_API_KEY;
    const r = await sendEmail({
      to: "test@example.com",
      subject: "Hello",
      html: "<p>Hi</p>",
    });
    expect(r.ok).toBe(true);
    if (r.ok) expect(r.id).toMatch(/^mock_/);
  });

  it("sanitizes subjects with CRLF (header injection guard)", async () => {
    delete process.env.RESEND_API_KEY;
    const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
    await sendEmail({
      to: "test@example.com",
      subject: "Hello\r\nBcc: evil@example.com",
      html: "<p>Hi</p>",
    });
    const logged = logSpy.mock.calls[0]?.[0] ?? "";
    expect(logged).not.toContain("\r");
    expect(logged).not.toContain("\n");
    logSpy.mockRestore();
  });
});

describe("ResendProvider.send", () => {
  it("posts to Resend API with bearer auth", async () => {
    process.env.RESEND_API_KEY = "re_test_key";
    process.env.EMAIL_FROM = "noreply@tuvi.test";
    const fetchSpy = vi
      .spyOn(global, "fetch")
      .mockResolvedValue(new Response(JSON.stringify({ id: "abc123" }), { status: 200 }));
    const r = await sendEmail({
      to: "user@example.com",
      subject: "Test",
      html: "<p>x</p>",
      tag: "reminder",
    });
    expect(r.ok).toBe(true);
    if (r.ok) expect(r.id).toBe("abc123");
    expect(fetchSpy).toHaveBeenCalledWith(
      "https://api.resend.com/emails",
      expect.objectContaining({
        method: "POST",
        headers: expect.objectContaining({
          Authorization: "Bearer re_test_key",
        }),
      })
    );
    const callArg = fetchSpy.mock.calls[0]?.[1];
    const body = JSON.parse((callArg as RequestInit).body as string);
    expect(body.from).toBe("noreply@tuvi.test");
    expect(body.to).toEqual(["user@example.com"]);
    expect(body.tags).toEqual([{ name: "category", value: "reminder" }]);
    fetchSpy.mockRestore();
  });

  it("returns error on non-2xx", async () => {
    process.env.RESEND_API_KEY = "re_test_key";
    const fetchSpy = vi
      .spyOn(global, "fetch")
      .mockResolvedValue(new Response("rate limited", { status: 429 }));
    const r = await sendEmail({
      to: "user@example.com",
      subject: "Test",
      html: "<p>x</p>",
    });
    expect(r.ok).toBe(false);
    if (!r.ok) expect(r.error).toContain("429");
    fetchSpy.mockRestore();
  });

  it("catches network errors", async () => {
    process.env.RESEND_API_KEY = "re_test_key";
    const fetchSpy = vi.spyOn(global, "fetch").mockRejectedValue(new Error("ECONNREFUSED"));
    const r = await sendEmail({
      to: "user@example.com",
      subject: "Test",
      html: "<p>x</p>",
    });
    expect(r.ok).toBe(false);
    if (!r.ok) expect(r.error).toContain("ECONNREFUSED");
    fetchSpy.mockRestore();
  });
});
