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

const mocks = vi.hoisted(() => ({
  createNotification: vi.fn(),
}))

vi.mock("../data-access", () => ({
  createNotification: mocks.createNotification,
}))

import { createInAppSender } from "./in-app-channel"
import type { NotificationPayload } from "../types"
import type { ChannelRecipient } from "./types"

describe("InAppChannelSender", () => {
  beforeEach(() => {
    vi.resetAllMocks()
  })

  const sender = createInAppSender()
  const recipient: ChannelRecipient = { userId: "user-1" }

  it("should map info type to message notification type", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ type: "message" })
    )
  })

  it("should map success type to message notification type", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "success",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ type: "message" })
    )
  })

  it("should map warning type to announcement notification type", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "warning",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ type: "announcement" })
    )
  })

  it("should map error type to grade notification type", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "error",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ type: "grade" })
    )
  })

  it("should return success result with messageId", async () => {
    mocks.createNotification.mockResolvedValue("notif-123")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
    }

    const result = await sender.send(payload, recipient)

    expect(result.channel).toBe("in_app")
    expect(result.success).toBe(true)
    expect(result.messageId).toBe("notif-123")
  })

  it("should return failure when recipient userId does not match payload userId", async () => {
    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
    }
    const wrongRecipient: ChannelRecipient = { userId: "user-2" }

    const result = await sender.send(payload, wrongRecipient)

    expect(result.success).toBe(false)
    expect(result.error).toContain("does not match")
  })

  it("should return failure when createNotification throws", async () => {
    mocks.createNotification.mockRejectedValue(new Error("DB error"))

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
    }

    const result = await sender.send(payload, recipient)

    expect(result.success).toBe(false)
    expect(result.error).toBe("DB error")
  })

  it("should use actionUrl as link when provided", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
      actionUrl: "/messages/123",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ link: "/messages/123" })
    )
  })

  it("should use null as link when actionUrl not provided", async () => {
    mocks.createNotification.mockResolvedValue("notif-1")

    const payload: NotificationPayload = {
      userId: "user-1",
      title: "Test",
      content: "Content",
      type: "info",
    }

    await sender.send(payload, recipient)

    expect(mocks.createNotification).toHaveBeenCalledWith(
      expect.objectContaining({ link: null })
    )
  })
})
