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

import { defineRoute } from "../shared/define-module"
import { HttpClient } from "./base"

// Mock fetch
const mockFetch = vi.fn()
global.fetch = mockFetch

describe("Content Types Support", () => {
  let client: HttpClient

  beforeEach(() => {
    client = new HttpClient({
      baseURL: "https://api.example.com",
      fetch: mockFetch,
    })
    mockFetch.mockClear()
  })

  describe("Request Types", () => {
    it("should handle JSON requests by default", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "application/json"]]),
        json: async () => ({ code: 0, data: { result: "success" } }),
      })

      await client.request("/test", {
        method: "POST",
        body: { test: "data" },
      })

      expect(mockFetch).toHaveBeenCalledWith(
        "https://api.example.com/test",
        expect.objectContaining({
          method: "POST",
          headers: expect.objectContaining({
            "Content-Type": "application/json",
          }),
          body: JSON.stringify({ test: "data" }),
        }),
      )
    })

    it("should handle FormData requests", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "application/json"]]),
        json: async () => ({ code: 0, data: { result: "success" } }),
      })

      const formData = new FormData()
      formData.append("file", new Blob(["test"]), "test.txt")

      await client.request("/upload", {
        method: "POST",
        body: formData,
        requestType: "formData",
      })

      expect(mockFetch).toHaveBeenCalledWith(
        "https://api.example.com/upload",
        expect.objectContaining({
          method: "POST",
          headers: expect.not.objectContaining({
            "Content-Type": expect.any(String),
          }),
          body: formData,
        }),
      )
    })

    it("should handle object as FormData", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "application/json"]]),
        json: async () => ({ code: 0, data: { result: "success" } }),
      })

      await client.request("/upload", {
        method: "POST",
        body: { name: "test", value: "123" },
        requestType: "formData",
      })

      expect(mockFetch).toHaveBeenCalledWith(
        "https://api.example.com/upload",
        expect.objectContaining({
          method: "POST",
          headers: expect.not.objectContaining({
            "Content-Type": expect.any(String),
          }),
          body: expect.any(FormData),
        }),
      )
    })

    it("should handle text requests", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "text/plain"]]),
        text: async () => "success",
      })

      await client.request("/text", {
        method: "POST",
        body: "plain text content",
        requestType: "text",
      })

      expect(mockFetch).toHaveBeenCalledWith(
        "https://api.example.com/text",
        expect.objectContaining({
          method: "POST",
          headers: expect.objectContaining({
            "Content-Type": "text/plain",
          }),
          body: "plain text content",
        }),
      )
    })
  })

  describe("Response Types", () => {
    it("should handle JSON responses", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "application/json"]]),
        json: async () => ({ code: 0, data: { result: "success" } }),
      })

      const result = await client.request("/test")

      expect(result).toEqual({ code: 0, data: { result: "success" } })
    })

    it("should handle text responses", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "text/plain"]]),
        text: async () => "plain text response",
      })

      const result = await client.request("/text")

      expect(result).toBe("plain text response")
    })

    it("should handle blob responses", async () => {
      const mockBlob = new Blob(["binary data"])
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "image/png"]]),
        blob: async () => mockBlob,
      })

      const result = await client.request("/image")

      expect(result).toBe(mockBlob)
    })

    it("should handle event stream responses", async () => {
      const mockResponse = {
        ok: true,
        headers: new Map([["content-type", "text/event-stream"]]),
        body: new ReadableStream(),
      }
      mockFetch.mockResolvedValueOnce(mockResponse)

      const result = await client.request("/stream")

      expect(result).toBe(mockResponse)
    })
  })

  describe("defineRoute with requestType", () => {
    it("should define route with requestType", () => {
      const route = defineRoute<FormData, { ok: 1 }>("POST", "/upload", {
        requestType: "formData",
      })

      expect(route).toEqual({
        method: "POST",
        path: "/upload",
        params: undefined,
        input: undefined,
        response: undefined,
        requestType: "formData",
      })
    })

    it("should define route with both requestType and responseType", () => {
      const route = defineRoute<string, Blob>("POST", "/convert", {
        requestType: "text",
      })

      expect(route).toEqual({
        method: "POST",
        path: "/convert",
        params: undefined,
        input: undefined,
        response: undefined,
        requestType: "text",
      })
    })
  })

  describe("Convenience methods", () => {
    it("should use postForm for form data", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        headers: new Map([["content-type", "application/json"]]),
        json: async () => ({ code: 0, data: { result: "success" } }),
      })

      const formData = new FormData()
      formData.append("file", new Blob(["test"]), "test.txt")

      await client.postForm("/upload", formData)

      expect(mockFetch).toHaveBeenCalledWith(
        "https://api.example.com/upload",
        expect.objectContaining({
          method: "POST",
          body: formData,
        }),
      )
    })

    it("should use getStream for event streams", async () => {
      const mockResponse = {
        ok: true,
        headers: new Map([["content-type", "text/event-stream"]]),
        body: new ReadableStream(),
      }
      mockFetch.mockResolvedValueOnce(mockResponse)

      const result = await client.getStream("/events")

      expect(result).toBe(mockResponse)
    })
  })
})
