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

import { useCustomFieldInputs } from '~/composables/useCustomFieldInputs'
import {
  shouldPersist,
  formatValueForSave,
} from '~/shared/utils/customFields'
import {
  mockCustomFieldDefs,
  mockCustomFieldValues,
  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-cfv-id', customField: { id: 'new-cf-id' } } })
})

// ---------------------------------------------------------------------------
// Field initialization
// ---------------------------------------------------------------------------

describe('field initialization', () => {
  it('merges all definitions with their values (6 defs → 6 allFields, 5 standalone fields)', () => {
    const { fields, allFields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

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

  it('preserves value for number type', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const numberField = fields.value.find(f => f.name === 'Tension nominale')
    expect(numberField?.value).toBe('220')
    expect(numberField?.type).toBe('number')
  })

  it('preserves value for boolean type', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

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

  it('preserves value for select type with options', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const selectField = fields.value.find(f => f.name === 'Indice de protection')
    expect(selectField?.value).toBe('IP65')
    expect(selectField?.type).toBe('select')
    expect(selectField?.options).toEqual(['IP54', 'IP55', 'IP65'])
  })

  it('preserves value for date type', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const dateField = fields.value.find(f => f.name === 'Date de calibration')
    expect(dateField?.value).toBe('2025-06-15')
    expect(dateField?.type).toBe('date')
  })

  it('preserves value for text type', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const textField = fields.value.find(f => f.name === 'Remarques techniques')
    expect(textField?.value).toBe('Roulement renforcé pour environnement humide')
    expect(textField?.type).toBe('text')
  })

  it('uses defaultValue when no persisted value exists', () => {
    // Pass empty values array so all fields use defaultValue
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref([]),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const numberField = fields.value.find(f => f.name === 'Tension nominale')
    expect(numberField?.value).toBe('220')

    const boolField = fields.value.find(f => f.name === 'Certifié CE')
    expect(boolField?.value).toBe('false')

    // No defaultValue → empty string
    const dateField = fields.value.find(f => f.name === 'Date de calibration')
    expect(dateField?.value).toBe('')
  })

  it('filters machineContextOnly in standalone context (allFields=6, fields=5)', () => {
    const { fields, allFields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    expect(allFields.value).toHaveLength(6)
    expect(fields.value).toHaveLength(5)
    expect(fields.value.every(f => !f.machineContextOnly)).toBe(true)
  })

  it('shows only machineContextOnly in machine context (1 field)', () => {
    const { fields } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'machine',
    })

    expect(fields.value).toHaveLength(1)
    expect(fields.value[0]?.name).toBe('Position sur machine')
    expect(fields.value[0]?.machineContextOnly).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// Boolean — the tricky case
// ---------------------------------------------------------------------------

describe('boolean — the tricky case', () => {
  it('saves "false" value via update (not ignored)', async () => {
    const { fields, update } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const boolField = fields.value.find(f => f.name === 'Certifié CE')!
    boolField.value = 'false'

    await update(boolField)

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

  it('persists boolean "false" in saveAll (not skipped)', async () => {
    // Only provide the boolean field def + value
    const boolDef = mockCustomFieldDefs[1]!
    const boolVal = { ...mockCustomFieldValues[1]!, value: 'false' }

    const { fields, saveAll } = useCustomFieldInputs({
      definitions: ref([boolDef]),
      values: ref([boolVal]),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

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

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

// ---------------------------------------------------------------------------
// Number zero
// ---------------------------------------------------------------------------

describe('number zero', () => {
  it('saves "0" value (not ignored)', async () => {
    const { fields, update } = useCustomFieldInputs({
      definitions: ref(mockMachineCustomFieldDefs),
      values: ref(mockMachineCustomFieldValues),
      entityType: 'machine',
      entityId: ref('cl-machine-1'),
      context: 'standalone',
    })

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

    await update(numField)

    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-003', { value: '0' })
  })
})

// ---------------------------------------------------------------------------
// Text empty string
// ---------------------------------------------------------------------------

describe('text empty string', () => {
  it('shouldPersist returns false for empty trimmed string', () => {
    const field = {
      customFieldId: 'cf-1',
      customFieldValueId: null,
      name: 'Notes',
      type: 'text',
      required: false,
      options: [],
      defaultValue: null,
      orderIndex: 0,
      machineContextOnly: false,
      value: '   ',
    }

    expect(shouldPersist(field)).toBe(false)
  })

  it('persists non-empty text value', () => {
    const field = {
      customFieldId: 'cf-1',
      customFieldValueId: null,
      name: 'Notes',
      type: 'text',
      required: false,
      options: [],
      defaultValue: null,
      orderIndex: 0,
      machineContextOnly: false,
      value: 'some text',
    }

    expect(shouldPersist(field)).toBe(true)
    expect(formatValueForSave(field)).toBe('some text')
  })
})

// ---------------------------------------------------------------------------
// Select
// ---------------------------------------------------------------------------

describe('select', () => {
  it('saves changed option value', async () => {
    const { fields, update } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const selectField = fields.value.find(f => f.name === 'Indice de protection')!
    selectField.value = 'IP55'

    await update(selectField)

    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('cfv-003', { value: 'IP55' })
  })
})

// ---------------------------------------------------------------------------
// Date
// ---------------------------------------------------------------------------

describe('date', () => {
  it('saves date value in correct format', async () => {
    const { fields, update } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const dateField = fields.value.find(f => f.name === 'Date de calibration')!
    dateField.value = '2026-01-20'

    await update(dateField)

    expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('cfv-004', { value: '2026-01-20' })
  })
})

// ---------------------------------------------------------------------------
// saveAll isolation
// ---------------------------------------------------------------------------

describe('saveAll isolation', () => {
  it('saves all fields independently without losing values', async () => {
    const { fields, saveAll } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    // Modify one field
    const numberField = fields.value.find(f => f.name === 'Tension nominale')!
    numberField.value = '380'

    const failed = await saveAll()
    expect(failed).toEqual([])

    // All persistable fields should have been saved
    // 5 fields in standalone context, all have values
    expect(mockUpdateCustomFieldValue.mock.calls.length).toBeGreaterThanOrEqual(4)

    // The modified field should have the new value
    const numberCall = mockUpdateCustomFieldValue.mock.calls.find(
      (c: any[]) => c[0] === 'cfv-001',
    )
    expect(numberCall?.[1]).toEqual({ value: '380' })

    // Another field should still have its original value
    const boolCall = mockUpdateCustomFieldValue.mock.calls.find(
      (c: any[]) => c[0] === 'cfv-002',
    )
    expect(boolCall?.[1]).toEqual({ value: 'true' })
  })

  it('upserts new value when no customFieldValueId exists', async () => {
    // Use defs without matching values — no customFieldValueId
    const defs = [mockCustomFieldDefs[0]!]

    const { saveAll } = useCustomFieldInputs({
      definitions: ref(defs),
      values: ref([]),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const failed = await saveAll()
    expect(failed).toEqual([])

    // Should use upsert since no customFieldValueId
    expect(mockUpsertCustomFieldValue).toHaveBeenCalledWith(
      'cf-def-001',
      'composant',
      'cl-comp-1',
      '220',
      undefined,
    )
  })

  it('returns failed field names on error', async () => {
    mockUpdateCustomFieldValue.mockResolvedValueOnce({ success: false })

    const defs = [mockCustomFieldDefs[0]!]
    const vals = [mockCustomFieldValues[0]!]

    const { saveAll } = useCustomFieldInputs({
      definitions: ref(defs),
      values: ref(vals),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    const failed = await saveAll()
    expect(failed).toEqual(['Tension nominale'])
  })
})

// ---------------------------------------------------------------------------
// requiredFilled validation
// ---------------------------------------------------------------------------

describe('requiredFilled validation', () => {
  it('returns true when required fields have values (including defaultValue)', () => {
    const { requiredFilled } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref(mockCustomFieldValues),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

    // "Tension nominale" is required and has value '220'
    expect(requiredFilled.value).toBe(true)
  })

  it('returns true when required field uses defaultValue', () => {
    // No values provided — required field should use defaultValue '220'
    const { requiredFilled } = useCustomFieldInputs({
      definitions: ref(mockCustomFieldDefs),
      values: ref([]),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

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

  it('returns false when required field has no value and no default', () => {
    // Create a required field with no default and no value
    const defs = [{
      id: 'cf-required-no-default',
      name: 'Required Field',
      type: 'text',
      required: true,
      options: [],
      defaultValue: null,
      orderIndex: 0,
      machineContextOnly: false,
    }]

    const { requiredFilled } = useCustomFieldInputs({
      definitions: ref(defs),
      values: ref([]),
      entityType: 'composant',
      entityId: ref('cl-comp-1'),
      context: 'standalone',
    })

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