import { describe, expect, it, vi } from "vitest";

import type { MailSettings } from "@/lib/mail-settings";
import { createSmtpTransport, sendContactMessage, sendMail, sendTestEmail } from "@/lib/mail";

function settings(overrides: Partial<MailSettings> = {}): MailSettings {
  return {
    smtp: { host: "smtp.example.com", port: 587, secure: false, username: "mailer", password: "secret", ...overrides.smtp },
    sender: { email: "hello@example.com", name: "Studio", ...overrides.sender },
    recipients: { contact: "contact@example.com", test: "test@example.com", ...overrides.recipients },
  };
}

function mockTransport() {
  const sendMailMock = vi.fn().mockResolvedValue({});
  const createTransport = vi.fn().mockReturnValue({ sendMail: sendMailMock });
  return { sendMailMock, createTransport };
}

describe("createSmtpTransport", () => {
  it("builds the transport with host, port, secure, and auth", () => {
    const { createTransport } = mockTransport();
    createSmtpTransport(settings({ smtp: { host: "h", port: 465, secure: true, username: "u", password: "p" } }), createTransport);
    expect(createTransport).toHaveBeenCalledWith({
      host: "h",
      port: 465,
      secure: true,
      auth: { user: "u", pass: "p" },
    });
  });

  it("requires host, username and password", () => {
    const { createTransport } = mockTransport();
    expect(() => createSmtpTransport(settings({ smtp: { host: "", port: 587, secure: false, username: "u", password: "p" } }), createTransport)).toThrow(/host/i);
    expect(() => createSmtpTransport(settings({ smtp: { host: "h", port: 587, secure: false, username: "", password: "p" } }), createTransport)).toThrow(/username/i);
    expect(() => createSmtpTransport(settings({ smtp: { host: "h", port: 587, secure: false, username: "u", password: "" } }), createTransport)).toThrow(/password/i);
  });
});

describe("sendMail", () => {
  it("formats the from header with the sender name", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings(), createTransport });
    expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ from: "Studio <hello@example.com>", to: "x@y.z" }));
  });

  it("omits the display name when sender name is blank", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings({ sender: { email: "hello@example.com", name: "" } }), createTransport });
    expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ from: "hello@example.com" }));
  });

  it("requires a from email", async () => {
    const { createTransport } = mockTransport();
    await expect(
      sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings({ sender: { email: "", name: "" } }), createTransport }),
    ).rejects.toThrow(/from email/i);
  });
});

describe("sendContactMessage", () => {
  it("targets the contact recipient with reply-to and full body", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendContactMessage(
      { locale: "en", name: "Jane", email: "jane@x.z", phone: "123", company: "Acme", message: "Hi there team." },
      { settings: settings(), createTransport },
    );
    const call = sendMailMock.mock.calls[0][0];
    expect(call).toMatchObject({ to: "contact@example.com", subject: "New contact message", replyTo: "jane@x.z" });
    expect(call.text).toContain("Name: Jane");
    expect(call.text).toContain("Phone: 123");
    expect(call.text).toContain("Company: Acme");
    expect(call.text).toContain("Hi there team.");
  });

  it("falls back to the test recipient when contact is empty", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendContactMessage(
      { locale: "de", name: "Jane", email: "jane@x.z", message: "Fallback works fine." },
      { settings: settings({ recipients: { contact: "", test: "fallback@x.z" } }), createTransport },
    );
    expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "fallback@x.z" }));
  });

  it("renders dashes for missing optional fields", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendContactMessage(
      { locale: "en", name: "Jane", email: "jane@x.z", message: "No phone or company." },
      { settings: settings(), createTransport },
    );
    const call = sendMailMock.mock.calls[0][0];
    expect(call.text).toContain("Phone: -");
    expect(call.text).toContain("Company: -");
  });
});

describe("sendTestEmail", () => {
  it("sends to the test recipient", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendTestEmail({ settings: settings(), createTransport });
    expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "test@example.com", subject: "SMTP test email" }));
  });

  it("falls back to the contact recipient when test is empty", async () => {
    const { sendMailMock, createTransport } = mockTransport();
    await sendTestEmail({ settings: settings({ recipients: { contact: "c@x.z", test: "" } }), createTransport });
    expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "c@x.z" }));
  });

  it("propagates transport failures", async () => {
    const createTransport = vi.fn().mockReturnValue({ sendMail: vi.fn().mockRejectedValue(new Error("Auth failed.")) });
    await expect(sendTestEmail({ settings: settings(), createTransport })).rejects.toThrow("Auth failed.");
  });
});
