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

import { useConstructeurLinks } from '~/composables/useConstructeurLinks'
import {
  mockLinkSKF,
  mockLinkFAG,
  mockConstructeurSKF,
  mockConstructeurFAG,
  wrapCollection,
} from '../fixtures/mockData'

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

const mockGet = vi.fn()
const mockPost = vi.fn()
const mockPatch = vi.fn()
const mockDel = vi.fn()

vi.mock('~/composables/useApi', () => ({
  useApi: () => ({
    get: mockGet,
    post: mockPost,
    patch: mockPatch,
    put: vi.fn(),
    delete: mockDel,
    postFormData: vi.fn(),
  }),
}))

beforeEach(() => {
  vi.clearAllMocks()
})

// ---------------------------------------------------------------------------
// fetchLinks
// ---------------------------------------------------------------------------
describe('fetchLinks', () => {
  it('returns parsed links with all properties for composant', async () => {
    const apiLinks = [
      {
        id: mockLinkSKF.linkId,
        constructeur: mockConstructeurSKF,
        supplierReference: mockLinkSKF.supplierReference,
      },
      {
        id: mockLinkFAG.linkId,
        constructeur: mockConstructeurFAG,
        supplierReference: mockLinkFAG.supplierReference,
      },
    ]
    mockGet.mockResolvedValue({ success: true, data: wrapCollection(apiLinks) })

    const { fetchLinks } = useConstructeurLinks()
    const result = await fetchLinks('composant', 'comp-001')

    expect(result).toHaveLength(2)
    expect(result[0]).toEqual({
      linkId: mockLinkSKF.linkId,
      constructeurId: mockConstructeurSKF.id,
      constructeur: mockConstructeurSKF,
      supplierReference: mockLinkSKF.supplierReference,
    })
    expect(result[1]).toEqual({
      linkId: mockLinkFAG.linkId,
      constructeurId: mockConstructeurFAG.id,
      constructeur: mockConstructeurFAG,
      supplierReference: mockLinkFAG.supplierReference,
    })
  })

  it('returns supplierReference as null when absent from API', async () => {
    const apiLinks = [
      {
        id: 'link-no-ref',
        constructeur: mockConstructeurSKF,
        // no supplierReference key
      },
    ]
    mockGet.mockResolvedValue({ success: true, data: wrapCollection(apiLinks) })

    const { fetchLinks } = useConstructeurLinks()
    const result = await fetchLinks('composant', 'comp-001')

    expect(result).toHaveLength(1)
    expect(result[0]!.supplierReference).toBeNull()
  })

  it.each([
    ['machine', '/machine_constructeur_links?machine=/api/machines/m-001', 'm-001'],
    ['product', '/product_constructeur_links?product=/api/products/p-001', 'p-001'],
    ['piece', '/piece_constructeur_links?piece=/api/pieces/pc-001', 'pc-001'],
    ['composant', '/composant_constructeur_links?composant=/api/composants/c-001', 'c-001'],
  ] as const)('uses correct endpoint for %s', async (entityType, expectedUrl, entityId) => {
    mockGet.mockResolvedValue({ success: true, data: wrapCollection([]) })

    const { fetchLinks } = useConstructeurLinks()
    await fetchLinks(entityType, entityId)

    expect(mockGet).toHaveBeenCalledWith(expectedUrl)
  })

  it('returns empty array on API failure', async () => {
    mockGet.mockResolvedValue({ success: false, data: null })

    const { fetchLinks } = useConstructeurLinks()
    const result = await fetchLinks('composant', 'comp-001')

    expect(result).toEqual([])
  })
})

// ---------------------------------------------------------------------------
// syncLinks — 3-way diff
// ---------------------------------------------------------------------------
describe('syncLinks', () => {
  it('creates new links via POST', async () => {
    mockPost.mockResolvedValue({ success: true })

    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [], [mockLinkSKF])

    expect(mockPost).toHaveBeenCalledWith('/composant_constructeur_links', {
      composant: '/api/composants/comp-001',
      constructeur: `/api/constructeurs/${mockConstructeurSKF.id}`,
      supplierReference: mockLinkSKF.supplierReference,
    })
    expect(mockDel).not.toHaveBeenCalled()
    expect(mockPatch).not.toHaveBeenCalled()
  })

  it('deletes removed links via DELETE', async () => {
    mockDel.mockResolvedValue({ success: true })

    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [mockLinkSKF], [])

    expect(mockDel).toHaveBeenCalledWith(`/composant_constructeur_links/${mockLinkSKF.linkId}`)
    expect(mockPost).not.toHaveBeenCalled()
    expect(mockPatch).not.toHaveBeenCalled()
  })

  it('patches when supplierReference changes (value to new value)', async () => {
    mockPatch.mockResolvedValue({ success: true })

    const updatedLink = { ...mockLinkSKF, supplierReference: 'NEW-REF-999' }
    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [mockLinkSKF], [updatedLink])

    expect(mockPatch).toHaveBeenCalledWith(`/composant_constructeur_links/${mockLinkSKF.linkId}`, {
      supplierReference: 'NEW-REF-999',
    })
    expect(mockPost).not.toHaveBeenCalled()
    expect(mockDel).not.toHaveBeenCalled()
  })

  it('patches when supplierReference changes from value to null', async () => {
    mockPatch.mockResolvedValue({ success: true })

    const updatedLink = { ...mockLinkSKF, supplierReference: null }
    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [mockLinkSKF], [updatedLink])

    expect(mockPatch).toHaveBeenCalledWith(`/composant_constructeur_links/${mockLinkSKF.linkId}`, {
      supplierReference: null,
    })
  })

  it('does nothing when links are identical (no API calls)', async () => {
    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [mockLinkSKF], [mockLinkSKF])

    expect(mockPost).not.toHaveBeenCalled()
    expect(mockDel).not.toHaveBeenCalled()
    expect(mockPatch).not.toHaveBeenCalled()
  })

  it('handles add + delete in same operation', async () => {
    mockPost.mockResolvedValue({ success: true })
    mockDel.mockResolvedValue({ success: true })

    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [mockLinkSKF], [mockLinkFAG])

    expect(mockDel).toHaveBeenCalledWith(`/composant_constructeur_links/${mockLinkSKF.linkId}`)
    expect(mockPost).toHaveBeenCalledWith('/composant_constructeur_links', {
      composant: '/api/composants/comp-001',
      constructeur: `/api/constructeurs/${mockConstructeurFAG.id}`,
      supplierReference: mockLinkFAG.supplierReference,
    })
    expect(mockPatch).not.toHaveBeenCalled()
  })

  it('handles empty original and empty form (no-op)', async () => {
    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [], [])

    expect(mockPost).not.toHaveBeenCalled()
    expect(mockDel).not.toHaveBeenCalled()
    expect(mockPatch).not.toHaveBeenCalled()
  })

  it('sends supplierReference as null when empty string', async () => {
    mockPost.mockResolvedValue({ success: true })

    const linkWithEmpty = { ...mockLinkFAG, supplierReference: '' }
    const { syncLinks } = useConstructeurLinks()
    await syncLinks('composant', 'comp-001', [], [linkWithEmpty])

    expect(mockPost).toHaveBeenCalledWith('/composant_constructeur_links', {
      composant: '/api/composants/comp-001',
      constructeur: `/api/constructeurs/${mockConstructeurFAG.id}`,
      supplierReference: null,
    })
  })

  it.each([
    ['machine', '/machine_constructeur_links', 'machine', '/api/machines/m-001', 'm-001'],
    ['product', '/product_constructeur_links', 'product', '/api/products/p-001', 'p-001'],
    ['piece', '/piece_constructeur_links', 'piece', '/api/pieces/pc-001', 'pc-001'],
    ['composant', '/composant_constructeur_links', 'composant', '/api/composants/c-001', 'c-001'],
  ] as const)('uses correct endpoint and entity IRI for %s', async (entityType, endpoint, key, entityIri, entityId) => {
    mockPost.mockResolvedValue({ success: true })
    mockDel.mockResolvedValue({ success: true })

    const { syncLinks } = useConstructeurLinks()
    await syncLinks(entityType, entityId, [mockLinkSKF], [mockLinkFAG])

    expect(mockDel).toHaveBeenCalledWith(`${endpoint}/${mockLinkSKF.linkId}`)
    expect(mockPost).toHaveBeenCalledWith(endpoint, {
      [key]: entityIri,
      constructeur: `/api/constructeurs/${mockConstructeurFAG.id}`,
      supplierReference: mockLinkFAG.supplierReference,
    })
  })
})
