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

import { useCustomFieldInputs } from '~/composables/useCustomFieldInputs'
import {
  mockMachineCustomFieldDefs,
  mockMachineCustomFieldValues,
} from '../fixtures/mockData'

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

const mockUpdateCustomFieldValue = vi.fn()
const mockUpsertCustomFieldValue = vi.fn()

vi.mock('~/composables/useCustomFields', () => ({
  useCustomFields: () => ({
    updateCustomFieldValue: mockUpdateCustomFieldValue,
    upsertCustomFieldValue: mockUpsertCustomFieldValue,
  }),
}))

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

beforeEach(() => {
  vi.clearAllMocks()
  mockUpdateCustomFieldValue.mockResolvedValue({ success: true })
  mockUpsertCustomFieldValue.mockResolvedValue({
    success: true,
    data: { id: 'new-mcfv-id', customField: { id: 'new-mcf-id' } },
  })
})

// ---------------------------------------------------------------------------
// Helper — create composable with machine context (no context filter)
// ---------------------------------------------------------------------------

function createMachineFields(
  defs = mockMachineCustomFieldDefs,
  vals = mockMachineCustomFieldValues,
  entityId = 'cl-machine-1',
) {
  return useCustomFieldInputs({
    definitions: ref(defs),
    values: ref(vals),
    entityType: 'machine',
    entityId: ref(entityId),
    // No context — machine custom fields don't use machineContextOnly filtering
  })
}

// ---------------------------------------------------------------------------
// Machine custom field initialization
// ---------------------------------------------------------------------------

describe('machine custom field initialization', () => {
  it('loads all machine custom fields with values (5 fields)', () => {
    const { fields } = createMachineFields()

    expect(fields.value).toHaveLength(5)
  })

  it('preserves text value (Numéro de série)', () => {
    const { fields } = createMachineFields()

    const textField = fields.value.find(f => f.name === 'Numéro de série')
    expect(textField?.value).toBe('SN-2025-001234')
    expect(textField?.type).toBe('text')
  })

  it('preserves boolean value (En service = true)', () => {
    const { fields } = createMachineFields()

    const boolField = fields.value.find(f => f.name === 'En service')
    expect(boolField?.value).toBe('true')
    expect(boolField?.type).toBe('boolean')
  })

  it('preserves number zero value (Puissance kW = 0)', () => {
    const { fields } = createMachineFields()

    const numField = fields.value.find(f => f.name === 'Puissance (kW)')
    expect(numField?.value).toBe('0')
    expect(numField?.type).toBe('number')
  })

  it('preserves select value (Catégorie ATEX = Zone 1)', () => {
    const { fields } = createMachineFields()

    const selectField = fields.value.find(f => f.name === 'Catégorie ATEX')
    expect(selectField?.value).toBe('Zone 1')
    expect(selectField?.type).toBe('select')
    expect(selectField?.options).toEqual(['Zone 0', 'Zone 1', 'Zone 2', 'Non classé'])
  })

  it('preserves date value (Date mise en service = 2025-01-15)', () => {
    const { fields } = createMachineFields()

    const dateField = fields.value.find(f => f.name === 'Date mise en service')
    expect(dateField?.value).toBe('2025-01-15')
    expect(dateField?.type).toBe('date')
  })
})

// ---------------------------------------------------------------------------
// Boolean checkbox — the critical test
// ---------------------------------------------------------------------------

describe('boolean checkbox — the critical test', () => {
  it('toggle true to false sends "false" (not deleted) via update()', async () => {
    const { fields, update } = createMachineFields()

    const boolField = fields.value.find(f => f.name === 'En service')!
    expect(boolField.value).toBe('true')

    // Toggle to false
    boolField.value = 'false'
    await update(boolField)

    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'false' })
  })

  it('toggle false to true sends "true"', async () => {
    // Start with boolean value = false
    const falseVal = { ...mockMachineCustomFieldValues[1]!, value: 'false' }
    const vals = mockMachineCustomFieldValues.map((v, i) => (i === 1 ? falseVal : v))

    const { fields, update } = createMachineFields(mockMachineCustomFieldDefs, vals)

    const boolField = fields.value.find(f => f.name === 'En service')!
    expect(boolField.value).toBe('false')

    // Toggle to true
    boolField.value = 'true'
    await update(boolField)

    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'true' })
  })

  it('boolean false is persisted in saveAll (not skipped)', async () => {
    // Only the boolean field with value "false"
    const boolDef = mockMachineCustomFieldDefs[1]!
    const boolVal = { ...mockMachineCustomFieldValues[1]!, value: 'false' }

    const { fields, saveAll } = createMachineFields([boolDef], [boolVal])

    expect(fields.value[0]?.value).toBe('false')

    const failed = await saveAll()
    expect(failed).toEqual([])
    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'false' })
  })
})

// ---------------------------------------------------------------------------
// Number zero — not lost
// ---------------------------------------------------------------------------

describe('number zero — not lost', () => {
  it('preserves zero value after load', () => {
    const { fields } = createMachineFields()

    const numField = fields.value.find(f => f.name === 'Puissance (kW)')!
    expect(numField.value).toBe('0')
  })

  it('saves zero value (not skipped) in saveAll', async () => {
    // Only the number field with value "0"
    const numDef = mockMachineCustomFieldDefs[2]!
    const numVal = mockMachineCustomFieldValues[2]!

    const { saveAll } = createMachineFields([numDef], [numVal])

    const failed = await saveAll()
    expect(failed).toEqual([])
    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-003', { value: '0' })
  })
})

// ---------------------------------------------------------------------------
// Select field
// ---------------------------------------------------------------------------

describe('select field', () => {
  it('preserves selected option', () => {
    const { fields } = createMachineFields()

    const selectField = fields.value.find(f => f.name === 'Catégorie ATEX')!
    expect(selectField.value).toBe('Zone 1')
  })

  it('uses defaultValue when no value exists', () => {
    // Use defs with a select that has a defaultValue
    const defsWithDefault = mockMachineCustomFieldDefs.map((d, i) =>
      i === 3 ? { ...d, defaultValue: 'Non classé' } : d,
    )

    // No values for the select field
    const valsWithoutSelect = mockMachineCustomFieldValues.filter(
      v => v.customField.name !== 'Catégorie ATEX',
    )

    const { fields } = createMachineFields(defsWithDefault, valsWithoutSelect)

    const selectField = fields.value.find(f => f.name === 'Catégorie ATEX')!
    expect(selectField.value).toBe('Non classé')
  })
})

// ---------------------------------------------------------------------------
// Field isolation
// ---------------------------------------------------------------------------

describe('field isolation', () => {
  it('updating one field does not change other field values', async () => {
    const { fields, update } = createMachineFields()

    // Snapshot original values
    const originalValues = fields.value.map(f => ({ name: f.name, value: f.value }))

    // Update only the text field
    const textField = fields.value.find(f => f.name === 'Numéro de série')!
    textField.value = 'SN-UPDATED-999'
    await update(textField)

    // All other fields should still have their original values
    for (const field of fields.value) {
      if (field.name === 'Numéro de série') continue
      const original = originalValues.find(o => o.name === field.name)
      expect(field.value).toBe(original?.value)
    }
  })

  it('saveAll preserves all field values even on partial failure', async () => {
    // Make the second call fail (boolean field)
    mockUpdateCustomFieldValue
      .mockResolvedValueOnce({ success: true }) // text — Numéro de série
      .mockResolvedValueOnce({ success: false }) // boolean — En service
      .mockResolvedValue({ success: true }) // rest succeed

    const { fields, saveAll } = createMachineFields()

    // Snapshot values before saveAll
    const valuesBefore = fields.value.map(f => ({ name: f.name, value: f.value }))

    const failed = await saveAll()

    // Only the boolean field should have failed
    expect(failed).toEqual(['En service'])

    // All field values should still be intact (not cleared or corrupted)
    for (const field of fields.value) {
      const before = valuesBefore.find(v => v.name === field.name)
      expect(field.value).toBe(before?.value)
    }
  })
})
