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

// ---------------------------------------------------------------------------
// Import under test (after mocks)
// ---------------------------------------------------------------------------

import { useMachineDetailData } from '~/composables/useMachineDetailData'

// ---------------------------------------------------------------------------
// Mock data — realistic /machines/{id}/structure response
// ---------------------------------------------------------------------------

const MACHINE_ID = 'cl-machine-abc123'
const SITE_ID = 'cl-site-nord-001'
const COMPONENT_LINK_ID = 'cl-mcl-001'
const PIECE_LINK_ID = 'cl-mpl-001'
const PRODUCT_LINK_ID = 'cl-mprl-001'
const COMPOSANT_ID = 'cl-comp-moteur-001'
const PIECE_ID = 'cl-piece-roul-001'
const PRODUCT_ID = 'cl-prod-graisse-001'
const CONSTRUCTEUR_ID = 'cstr-skf-001'

const mockConstructeurSKF = {
  id: CONSTRUCTEUR_ID,
  name: 'SKF',
  email: 'contact@skf.com',
  phone: '+33 1 23 45 67 89',
}

const mockStructureResponse = {
  success: true,
  data: {
    machine: {
      id: MACHINE_ID,
      name: 'Presse hydraulique PH-200',
      reference: 'MACH-PH-200',
      prix: 150000,
      siteId: SITE_ID,
      site: { id: SITE_ID, name: 'Usine Nord' },
      documents: [{ id: 'doc-001', name: 'Manuel PH-200.pdf', type: 'manual' }],
      customFieldValues: [
        {
          id: 'mcfv-001',
          value: 'SN-2025-PH200',
          customField: {
            id: 'mcf-001',
            name: 'Serial Number',
            type: 'text',
            required: true,
            options: [],
            defaultValue: null,
            orderIndex: 0,
            machineContextOnly: false,
          },
        },
      ],
      customFields: [
        {
          id: 'mcf-001',
          name: 'Serial Number',
          type: 'text',
          required: true,
          options: [],
          defaultValue: null,
          orderIndex: 0,
          machineContextOnly: false,
        },
      ],
      constructeurs: [
        {
          id: 'cl-mconst-001',
          constructeur: mockConstructeurSKF,
          supplierReference: 'SKF-PH200',
        },
      ],
    },
    componentLinks: [
      {
        id: COMPONENT_LINK_ID,
        composant: {
          id: COMPOSANT_ID,
          name: 'Moteur principal',
          reference: 'COMP-MOT-001',
          prix: 12500,
          typeComposant: { id: 'tc-moteur', name: 'Moteur electrique' },
          constructeurs: [mockConstructeurSKF],
          constructeurIds: [CONSTRUCTEUR_ID],
          documents: [],
          customFields: [
            {
              definitionId: 'cf-comp-001',
              name: 'Tension nominale',
              type: 'number',
              value: '380',
            },
          ],
          customFieldValues: [],
        },
        overrides: {
          name: 'Moteur principal PH-200',
          reference: 'COMP-MOT-PH200',
          prix: 13000,
        },
        contextCustomFields: [
          {
            id: 'ctx-cf-001',
            name: 'Position sur machine',
            type: 'text',
            machineContextOnly: true,
          },
        ],
        contextCustomFieldValues: [
          {
            id: 'ctx-cfv-001',
            value: 'Bloc moteur gauche',
            customField: {
              id: 'ctx-cf-001',
              name: 'Position sur machine',
              type: 'text',
              machineContextOnly: true,
            },
          },
        ],
        pieceLinks: [
          {
            id: PIECE_LINK_ID,
            piece: {
              id: PIECE_ID,
              name: 'Roulement 6205',
              reference: 'ROUL-6205',
              prix: 45.90,
              typePiece: { id: 'tp-bearing', name: 'Roulement' },
              constructeurs: [mockConstructeurSKF],
              documents: [],
              customFields: [],
            },
            overrides: {
              name: 'Roulement 6205-RS',
            },
            quantity: 2,
            parentComponentLinkId: COMPONENT_LINK_ID,
            contextCustomFields: [],
            contextCustomFieldValues: [
              {
                id: 'ctx-cfv-piece-001',
                value: 'Cote entrainement',
                customField: {
                  id: 'ctx-cf-piece-001',
                  name: 'Emplacement',
                  type: 'text',
                  machineContextOnly: true,
                },
              },
            ],
          },
        ],
        childLinks: [],
      },
    ],
    pieceLinks: [],
    productLinks: [
      {
        id: PRODUCT_LINK_ID,
        product: {
          id: PRODUCT_ID,
          name: 'Graisse LGMT2',
          reference: 'LUB-LGMT2',
          prix: 45.90,
        },
        overrides: null,
      },
    ],
  },
}

// Response with NO overrides — for fallback testing
const mockStructureNoOverrides = {
  success: true,
  data: {
    machine: {
      ...mockStructureResponse.data.machine,
    },
    componentLinks: [
      {
        id: COMPONENT_LINK_ID,
        composant: {
          id: COMPOSANT_ID,
          name: 'Moteur principal',
          reference: 'COMP-MOT-001',
          prix: 12500,
          typeComposant: { id: 'tc-moteur', name: 'Moteur electrique' },
          constructeurs: [],
          documents: [],
          customFields: [],
          customFieldValues: [],
        },
        overrides: null,
        contextCustomFields: [],
        contextCustomFieldValues: [],
        pieceLinks: [
          {
            id: PIECE_LINK_ID,
            piece: {
              id: PIECE_ID,
              name: 'Roulement 6205',
              reference: 'ROUL-6205',
              prix: 45.90,
              typePiece: { id: 'tp-bearing', name: 'Roulement' },
              constructeurs: [],
              documents: [],
              customFields: [],
            },
            overrides: null,
            quantity: 1,
            parentComponentLinkId: COMPONENT_LINK_ID,
            contextCustomFields: [],
            contextCustomFieldValues: [],
          },
        ],
        childLinks: [],
      },
    ],
    pieceLinks: [],
    productLinks: [],
  },
}

// ---------------------------------------------------------------------------
// Mocks — all composables used by useMachineDetailData
// ---------------------------------------------------------------------------

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

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

vi.mock('~/composables/useToast', () => ({
  useToast: () => ({
    showSuccess: vi.fn(),
    showError: vi.fn(),
    showInfo: vi.fn(),
    showToast: vi.fn(),
    toasts: { value: [] },
    clearAll: vi.fn(),
  }),
}))

vi.mock('~/composables/useMachines', () => ({
  useMachines: () => ({
    updateMachine: vi.fn().mockResolvedValue({ success: true }),
    updateStructure: vi.fn().mockResolvedValue({ success: true }),
  }),
}))

vi.mock('~/composables/useComposants', () => ({
  useComposants: () => ({
    updateComposant: vi.fn().mockResolvedValue({ success: true }),
  }),
}))

vi.mock('~/composables/usePieces', () => ({
  usePieces: () => ({
    updatePiece: vi.fn().mockResolvedValue({ success: true }),
  }),
}))

vi.mock('~/composables/useComponentTypes', () => ({
  useComponentTypes: () => ({
    componentTypes: ref([
      { id: 'tc-moteur', name: 'Moteur electrique' },
    ]),
    loadComponentTypes: vi.fn().mockResolvedValue(undefined),
  }),
}))

vi.mock('~/composables/usePieceTypes', () => ({
  usePieceTypes: () => ({
    pieceTypes: ref([
      { id: 'tp-bearing', name: 'Roulement' },
    ]),
    loadPieceTypes: vi.fn().mockResolvedValue(undefined),
  }),
}))

vi.mock('~/composables/useCustomFields', () => ({
  useCustomFields: () => ({
    upsertCustomFieldValue: vi.fn().mockResolvedValue({ success: true }),
    updateCustomFieldValue: vi.fn().mockResolvedValue({ success: true }),
  }),
}))

vi.mock('~/composables/useConstructeurs', () => ({
  useConstructeurs: () => ({
    constructeurs: ref([mockConstructeurSKF]),
    loadConstructeurs: vi.fn().mockResolvedValue(undefined),
  }),
}))

vi.mock('~/composables/useSites', () => ({
  useSites: () => ({
    sites: ref([{ id: SITE_ID, name: 'Usine Nord' }]),
    loadSites: vi.fn().mockResolvedValue(undefined),
  }),
}))

vi.mock('~/composables/useProducts', () => ({
  useProducts: () => ({
    products: ref([
      { id: PRODUCT_ID, name: 'Graisse LGMT2', reference: 'LUB-LGMT2', prix: 45.90 },
    ]),
    loadProducts: vi.fn().mockResolvedValue(undefined),
  }),
}))

vi.mock('~/composables/useDocuments', () => ({
  useDocuments: () => ({
    uploadDocuments: vi.fn().mockResolvedValue({ success: true }),
    deleteDocument: vi.fn().mockResolvedValue({ success: true }),
    loadDocumentsByMachine: vi.fn().mockResolvedValue({ success: true, data: [] }),
    loadDocumentsByProduct: vi.fn().mockResolvedValue({ success: true, data: [] }),
  }),
}))

vi.mock('~/composables/useConstructeurLinks', () => ({
  useConstructeurLinks: () => ({
    fetchLinks: vi.fn().mockResolvedValue([]),
    syncLinks: vi.fn().mockResolvedValue({ success: true }),
  }),
}))

vi.mock('~/utils/printTemplates/machineReport', () => ({
  buildMachinePrintContext: vi.fn(),
  buildMachinePrintHtml: vi.fn().mockReturnValue('<html></html>'),
}))

vi.mock('~/utils/documentPreview', () => ({
  canPreviewDocument: vi.fn().mockReturnValue(false),
}))

vi.mock('~/shared/utils/documentDisplayUtils', () => ({
  downloadDocument: vi.fn(),
}))

// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------

beforeEach(() => {
  vi.clearAllMocks()
  mockGet.mockResolvedValue(mockStructureResponse)
})

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

async function loadAndReturn(responseOverride?: unknown) {
  if (responseOverride) {
    mockGet.mockResolvedValue(responseOverride)
  }
  const result = useMachineDetailData(MACHINE_ID)
  await result.loadMachineData()
  return result
}

// ===========================================================================
// 1. Hierarchy loading
// ===========================================================================

describe('hierarchy loading', () => {
  it('loads machine with all core fields', async () => {
    const { machine, machineName, machineReference, machineSiteId } = await loadAndReturn()

    expect(machine.value).not.toBeNull()
    expect(machine.value!.id).toBe(MACHINE_ID)
    expect(machine.value!.name).toBe('Presse hydraulique PH-200')
    expect(machine.value!.reference).toBe('MACH-PH-200')
    expect(machine.value!.prix).toBe(150000)
    expect(machineName.value).toBe('Presse hydraulique PH-200')
    expect(machineReference.value).toBe('MACH-PH-200')
    expect(machineSiteId.value).toBe(SITE_ID)
  })

  it('calls GET /machines/{id}/structure', async () => {
    await loadAndReturn()

    expect(mockGet).toHaveBeenCalledWith(`/machines/${MACHINE_ID}/structure`)
  })

  it('loads componentLinks from structure response', async () => {
    const { machineComponentLinks } = await loadAndReturn()

    expect(machineComponentLinks.value).toHaveLength(1)
    expect(machineComponentLinks.value[0]!.id).toBe(COMPONENT_LINK_ID)
  })

  it('builds component hierarchy with composant data', async () => {
    const { components } = await loadAndReturn()

    expect(components.value.length).toBeGreaterThanOrEqual(1)
    const comp = components.value[0]!
    expect(comp.composantId).toBe(COMPOSANT_ID)
  })

  it('loads piece links nested under their parent componentLink', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    const pieces = comp.pieces as Record<string, unknown>[]
    expect(pieces).toBeDefined()
    expect(pieces.length).toBeGreaterThanOrEqual(1)

    const piece = pieces[0]!
    expect(piece.pieceId).toBe(PIECE_ID)
    expect(piece.parentComponentLinkId).toBe(COMPONENT_LINK_ID)
  })

  it('preserves piece quantity', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    const pieces = comp.pieces as Record<string, unknown>[]
    const piece = pieces[0]!
    expect(piece.quantity).toBe(2)
  })

  it('loads product links at machine level', async () => {
    const { machineProductLinks } = await loadAndReturn()

    expect(machineProductLinks.value).toHaveLength(1)
    expect(machineProductLinks.value[0]!.id).toBe(PRODUCT_LINK_ID)
  })

  it('preserves machine documents', async () => {
    const { machine } = await loadAndReturn()

    const docs = machine.value!.documents as unknown[]
    expect(docs).toHaveLength(1)
  })

  it('preserves machine customFieldValues', async () => {
    const { machine } = await loadAndReturn()

    const cfv = machine.value!.customFieldValues as Record<string, unknown>[]
    expect(cfv).toHaveLength(1)
    expect((cfv[0] as any).value).toBe('SN-2025-PH200')
  })

  it('sets loading to false after data load', async () => {
    const { loading } = await loadAndReturn()

    expect(loading.value).toBe(false)
  })

  it('handles failed API response gracefully', async () => {
    const { machine, components, pieces } = await loadAndReturn({
      success: false,
      error: 'Not found',
    })

    expect(machine.value).toBeNull()
    expect(components.value).toEqual([])
    expect(pieces.value).toEqual([])
  })

  it('handles invalid machine payload gracefully', async () => {
    const { machine } = await loadAndReturn({
      success: true,
      data: null,
    })

    expect(machine.value).toBeNull()
  })
})

// ===========================================================================
// 2. Overrides
// ===========================================================================

describe('overrides on component links', () => {
  it('uses nameOverride when present', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    expect(comp.name).toBe('Moteur principal PH-200')
  })

  it('falls back to composant.name when nameOverride is null', async () => {
    const { components } = await loadAndReturn(mockStructureNoOverrides)

    const comp = components.value[0]!
    expect(comp.name).toBe('Moteur principal')
  })

  it('uses referenceOverride when present', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    expect(comp.reference).toBe('COMP-MOT-PH200')
  })

  it('falls back to composant.reference when referenceOverride is null', async () => {
    const { components } = await loadAndReturn(mockStructureNoOverrides)

    const comp = components.value[0]!
    expect(comp.reference).toBe('COMP-MOT-001')
  })

  it('uses prixOverride when present', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    expect(comp.prix).toBe(13000)
  })

  it('falls back to composant.prix when prixOverride is null', async () => {
    const { components } = await loadAndReturn(mockStructureNoOverrides)

    const comp = components.value[0]!
    expect(comp.prix).toBe(12500)
  })
})

describe('overrides on piece links', () => {
  it('uses piece nameOverride when present', async () => {
    const { components } = await loadAndReturn()

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    expect(piece.name).toBe('Roulement 6205-RS')
  })

  it('falls back to piece.name when nameOverride is null', async () => {
    const { components } = await loadAndReturn(mockStructureNoOverrides)

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    expect(piece.name).toBe('Roulement 6205')
  })

  it('preserves piece reference from underlying entity when no override', async () => {
    const { components } = await loadAndReturn()

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    // The override only has name, so reference comes from the piece entity
    expect(piece.reference).toBe('ROUL-6205')
  })

  it('preserves piece prix from underlying entity when no override', async () => {
    const { components } = await loadAndReturn()

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    expect(piece.prix).toBe(45.90)
  })
})

// ===========================================================================
// 3. Custom field values on links (context fields)
// ===========================================================================

describe('contextCustomFieldValues on component links', () => {
  it('loads contextCustomFieldValues on component hierarchy nodes', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    const ctxValues = comp.contextCustomFieldValues as Record<string, unknown>[]
    expect(ctxValues).toBeDefined()
    expect(ctxValues).toHaveLength(1)
    expect((ctxValues[0] as any).value).toBe('Bloc moteur gauche')
    expect((ctxValues[0] as any).customField.name).toBe('Position sur machine')
  })

  it('loads contextCustomFields definitions on component hierarchy nodes', async () => {
    const { components } = await loadAndReturn()

    const comp = components.value[0]!
    const ctxFields = comp.contextCustomFields as Record<string, unknown>[]
    expect(ctxFields).toBeDefined()
    expect(ctxFields).toHaveLength(1)
    expect((ctxFields[0] as any).name).toBe('Position sur machine')
    expect((ctxFields[0] as any).machineContextOnly).toBe(true)
  })
})

describe('contextCustomFieldValues on piece links', () => {
  it('loads contextCustomFieldValues on piece hierarchy nodes', async () => {
    const { components } = await loadAndReturn()

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    const ctxValues = piece.contextCustomFieldValues as Record<string, unknown>[]
    expect(ctxValues).toBeDefined()
    expect(ctxValues).toHaveLength(1)
    expect((ctxValues[0] as any).value).toBe('Cote entrainement')
  })

  it('has empty contextCustomFieldValues when none provided', async () => {
    const { components } = await loadAndReturn(mockStructureNoOverrides)

    const piece = (components.value[0]!.pieces as Record<string, unknown>[])[0]!
    const ctxValues = piece.contextCustomFieldValues as Record<string, unknown>[]
    expect(ctxValues).toEqual([])
  })
})

// ===========================================================================
// 4. Constructeur links on machine
// ===========================================================================

describe('constructeur links on machine', () => {
  it('parses constructeur links from machine data', async () => {
    const { constructeurLinks } = await loadAndReturn()

    expect(constructeurLinks.value).toHaveLength(1)
    expect(constructeurLinks.value[0]!.constructeurId).toBe(CONSTRUCTEUR_ID)
    expect(constructeurLinks.value[0]!.supplierReference).toBe('SKF-PH200')
  })

  it('populates machineConstructeurIds from links', async () => {
    const { machineConstructeurIds } = await loadAndReturn()

    expect(machineConstructeurIds.value).toContain(CONSTRUCTEUR_ID)
  })

  it('stores original constructeur links for cancel rollback', async () => {
    const { originalConstructeurLinks } = await loadAndReturn()

    expect(originalConstructeurLinks.value).toHaveLength(1)
    expect(originalConstructeurLinks.value[0]!.constructeurId).toBe(CONSTRUCTEUR_ID)
  })

  it('hasMachineConstructeur is true when constructeur present', async () => {
    const { hasMachineConstructeur } = await loadAndReturn()

    expect(hasMachineConstructeur.value).toBe(true)
  })

  it('resolves constructeur display objects', async () => {
    const { machineConstructeursDisplay } = await loadAndReturn()

    expect(machineConstructeursDisplay.value.length).toBeGreaterThanOrEqual(1)
    const display = machineConstructeursDisplay.value[0] as any
    expect(display.name).toBe('SKF')
  })
})

// ===========================================================================
// 5. Site (required)
// ===========================================================================

describe('site loaded with machine data', () => {
  it('machineSiteId is populated from machine payload', async () => {
    const { machineSiteId } = await loadAndReturn()

    expect(machineSiteId.value).toBe(SITE_ID)
  })

  it('sites ref is available for dropdowns', async () => {
    const { sites } = await loadAndReturn()

    expect(sites.value).toHaveLength(1)
    expect((sites.value[0] as any).name).toBe('Usine Nord')
  })

  it('machine.site object is preserved in machine ref', async () => {
    const { machine } = await loadAndReturn()

    const site = machine.value!.site as Record<string, unknown>
    expect(site.id).toBe(SITE_ID)
    expect(site.name).toBe('Usine Nord')
  })
})

// ===========================================================================
// 6. UI state defaults
// ===========================================================================

describe('UI state defaults', () => {
  it('isEditMode starts as false', () => {
    const { isEditMode } = useMachineDetailData(MACHINE_ID)
    expect(isEditMode.value).toBe(false)
  })

  it('saving starts as false', () => {
    const { saving } = useMachineDetailData(MACHINE_ID)
    expect(saving.value).toBe(false)
  })

  it('loading starts as true', () => {
    const { loading } = useMachineDetailData(MACHINE_ID)
    expect(loading.value).toBe(true)
  })
})
