import { describe, expect, it, vi } from "vitest"
import { flushPromises, mount } from "@vue/test-utils"
import type { Connector } from "@/definitions/shared/connector"
import IntegrationConnectorPicker from "./IntegrationConnectorPicker.vue"

const SAMPLE: Connector[] = [
  {
    id: "slack-1",
    label: "Slack — Eng",
    icon: "slack",
    type: "slack",
    status: "connected",
  },
  {
    id: "sf-1",
    label: "Salesforce Prod",
    icon: "cloud",
    type: "salesforce",
    status: "disconnected",
  },
  {
    id: "gh-1",
    label: "GitHub",
    icon: "github",
    type: "github",
    status: "error",
  },
]

interface Deferred<T> {
  promise: Promise<T>
  resolve: (v: T) => void
  reject: (e: unknown) => void
}

function withDefer<T>(): Deferred<T> {
  let resolve!: (v: T) => void
  let reject!: (e: unknown) => void
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })
  return { promise, resolve, reject }
}

interface ExposedApi {
  refresh: () => Promise<void>
  handleConnect: (id: string, event?: Event) => Promise<void>
}

describe("IntegrationConnectorPicker (props-based composition)", () => {
  describe("rendering", () => {
    it("mounts without error", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "Connector", onLoadConnectors },
      })
      expect(wrapper.exists()).toBe(true)
      await flushPromises()
    })

    it("renders the label via FieldFrame", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "Connector", onLoadConnectors },
      })
      expect(wrapper.text()).toContain("Connector")
      await flushPromises()
    })

    it("marks required via FieldFrame", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "Connector", required: true, onLoadConnectors },
      })
      expect(wrapper.html()).toContain("after:content-['*']")
      await flushPromises()
    })

    it("renders help text", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "Connector", helpText: "Pick one", onLoadConnectors },
      })
      expect(wrapper.find("[data-testid='field-frame-help']").text()).toBe(
        "Pick one",
      )
      await flushPromises()
    })

    it("renders errors from props", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", errors: ["Required"], onLoadConnectors },
      })
      expect(wrapper.text()).toContain("Required")
      await flushPromises()
    })

    it("hides the label when hideLabel is true", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", hideLabel: true, onLoadConnectors },
      })
      expect(wrapper.find("[data-testid='field-frame-label']").exists()).toBe(
        false,
      )
      await flushPromises()
    })
  })

  describe("loading lifecycle", () => {
    it("calls onLoadConnectors on mount", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors },
      })
      await flushPromises()
      expect(onLoadConnectors).toHaveBeenCalledTimes(1)
    })

    it("shows the loader while the load promise is pending", async () => {
      const deferred = withDefer<Connector[]>()
      const onLoadConnectors = vi.fn(() => deferred.promise)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors },
      })
      // The picker's loader lives in the anchor next to the chevron and
      // also in the portalled panel — checking the anchor loader.
      // The Combobox panel is portalled, so we assert no crash and the
      // refresh API surface is wired.
      const vm = wrapper.vm as unknown as ExposedApi
      expect(typeof vm.refresh).toBe("function")
      deferred.resolve(SAMPLE)
      await flushPromises()
    })

    it("captures the error path when onLoadConnectors rejects", async () => {
      const onLoadConnectors = vi
        .fn()
        .mockRejectedValueOnce(new Error("boom"))
        .mockResolvedValueOnce(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as ExposedApi
      await vm.refresh()
      await flushPromises()
      expect(onLoadConnectors).toHaveBeenCalledTimes(2)
    })
  })

  describe("modelValue display", () => {
    it("renders the placeholder when no connector is selected", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", placeholder: "Pick a connector", onLoadConnectors },
      })
      await flushPromises()
      expect(
        wrapper
          .find("[data-testid='integration-connector-picker-placeholder']")
          .text(),
      ).toBe("Pick a connector")
    })

    it("renders the selected connector label in the trigger", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", modelValue: "slack-1", onLoadConnectors },
      })
      await flushPromises()
      expect(
        wrapper
          .find("[data-testid='integration-connector-picker-label']")
          .text(),
      ).toBe("Slack — Eng")
    })

    it("renders a status dot for the selected connector", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", modelValue: "gh-1", onLoadConnectors },
      })
      await flushPromises()
      const dot = wrapper.find(
        "[data-testid='integration-connector-picker-status']",
      )
      expect(dot.exists()).toBe(true)
      expect(dot.attributes("data-status")).toBe("error")
    })

    it("emits update:modelValue when the Combobox selects a connector", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors },
      })
      await flushPromises()
      const combobox = wrapper.findComponent({ name: "Combobox" })
      combobox.vm.$emit("update:modelValue", "sf-1")
      await flushPromises()
      expect(wrapper.emitted("update:modelValue")?.[0]).toEqual(["sf-1"])
    })
  })

  describe("onConnect flow", () => {
    it("calls onConnect with the connector id when handleConnect runs", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const onConnect = vi.fn().mockResolvedValue(undefined)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors, onConnect },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as ExposedApi
      await vm.handleConnect("sf-1")
      await flushPromises()
      expect(onConnect).toHaveBeenCalledWith("sf-1")
    })

    it("re-runs onLoadConnectors after onConnect resolves", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const onConnect = vi.fn().mockResolvedValue(undefined)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors, onConnect },
      })
      await flushPromises()
      onLoadConnectors.mockClear()
      const vm = wrapper.vm as unknown as ExposedApi
      await vm.handleConnect("sf-1")
      await flushPromises()
      expect(onLoadConnectors).toHaveBeenCalledTimes(1)
    })

    it("does nothing on Connect when onConnect is not provided", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue(SAMPLE)
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", onLoadConnectors },
      })
      await flushPromises()
      onLoadConnectors.mockClear()
      const vm = wrapper.vm as unknown as ExposedApi
      await vm.handleConnect("sf-1")
      await flushPromises()
      expect(onLoadConnectors).not.toHaveBeenCalled()
    })
  })

  describe("forwarded props", () => {
    it("accepts disabled without crashing", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", disabled: true, onLoadConnectors },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
    })

    it("accepts readonly without crashing", async () => {
      const onLoadConnectors = vi.fn().mockResolvedValue([])
      const wrapper = mount(IntegrationConnectorPicker, {
        props: { label: "X", readonly: true, onLoadConnectors },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
    })
  })
})
