import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, type VueWrapper } from '@vue/test-utils'
import { nextTick, ref } from 'vue'

import PieceModelStructureEditor from '~/components/PieceModelStructureEditor.vue'

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

vi.mock('~/composables/useProductTypes', () => ({
  useProductTypes: () => ({
    productTypes: ref([]),
    loadingProductTypes: ref(false),
    loadProductTypes: vi.fn().mockResolvedValue({ success: true }),
    createProductType: vi.fn(),
    updateProductType: vi.fn(),
    deleteProductType: vi.fn(),
    getProductTypes: () => [],
    isProductTypeLoading: () => false,
  }),
}))

vi.mock('~/shared/modelUtils', () => ({
  normalizePieceStructureForSave: (s: any) => s ?? { customFields: [] },
}))

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

function mountEditor(props: Record<string, any> = {}) {
  return mount(PieceModelStructureEditor, {
    props: {
      modelValue: { customFields: [], products: [] },
      ...props,
    },
  })
}

function getAddFieldButton(wrapper: VueWrapper) {
  // The second "Ajouter" button is for custom fields
  const buttons = wrapper.findAll('button').filter(b => b.text().includes('Ajouter'))
  return buttons[buttons.length - 1]
}

function getAddProductButton(wrapper: VueWrapper) {
  // The first "Ajouter" button is for products
  const buttons = wrapper.findAll('button').filter(b => b.text().includes('Ajouter'))
  return buttons[0]
}

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

// ---------------------------------------------------------------------------
// Empty state
// ---------------------------------------------------------------------------
describe('empty state', () => {
  it('renders with no fields and shows empty message', () => {
    const wrapper = mountEditor()
    expect(wrapper.text()).toContain('Aucun champ personnalisé')
  })

  it('renders with no products and shows empty message', () => {
    const wrapper = mountEditor()
    expect(wrapper.text()).toContain('Aucun produit défini')
  })

  it('shows add field button', () => {
    const wrapper = mountEditor()
    expect(getAddFieldButton(wrapper).exists()).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// Add custom field
// ---------------------------------------------------------------------------
describe('add custom field', () => {
  it('adds a new empty field on button click', async () => {
    const wrapper = mountEditor()
    await getAddFieldButton(wrapper).trigger('click')
    await nextTick()

    const nameInputs = wrapper.findAll('input[type="text"]')
    expect(nameInputs.length).toBeGreaterThanOrEqual(1)
    expect(wrapper.text()).not.toContain('Aucun champ personnalisé')
  })

  it('new field has type "text" by default', async () => {
    const wrapper = mountEditor()
    await getAddFieldButton(wrapper).trigger('click')
    await nextTick()

    const selects = wrapper.findAll('select')
    // The last select should be the type select for the new field
    const typeSelect = selects[selects.length - 1]
    expect((typeSelect.element as HTMLSelectElement).value).toBe('text')
  })

  it('emits update:modelValue after adding field', async () => {
    const wrapper = mountEditor()
    await getAddFieldButton(wrapper).trigger('click')
    await nextTick()

    expect(wrapper.emitted('update:modelValue')).toBeTruthy()
  })
})

// ---------------------------------------------------------------------------
// Remove custom field
// ---------------------------------------------------------------------------
describe('remove custom field', () => {
  it('removes field on delete button click', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Weight', type: 'number', required: false, orderIndex: 0 }],
        products: [],
      },
    })

    // Find the delete button (btn-error)
    const deleteBtn = wrapper.find('button.btn-error')
    expect(deleteBtn.exists()).toBe(true)

    await deleteBtn.trigger('click')
    await nextTick()

    expect(wrapper.text()).toContain('Aucun champ personnalisé')
    expect(wrapper.emitted('update:modelValue')).toBeTruthy()
  })
})

// ---------------------------------------------------------------------------
// Field name editing
// ---------------------------------------------------------------------------
describe('field name editing', () => {
  it('updates field name on input', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Old Name', type: 'text', required: false, orderIndex: 0 }],
        products: [],
      },
    })

    const nameInput = wrapper.find('input[type="text"]')
    await nameInput.setValue('New Name')
    await nextTick()

    const events = wrapper.emitted('update:modelValue')
    expect(events).toBeTruthy()
    const lastPayload = events![events!.length - 1][0] as any
    expect(lastPayload.customFields[0].name).toBe('New Name')
  })
})

// ---------------------------------------------------------------------------
// Field type change
// ---------------------------------------------------------------------------
describe('field type change', () => {
  it('changes field type via select', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Size', type: 'text', required: false, orderIndex: 0 }],
        products: [],
      },
    })

    // Find the type select (not the product type select)
    const selects = wrapper.findAll('select')
    const typeSelect = selects[selects.length - 1]
    await typeSelect.setValue('number')
    await nextTick()

    const events = wrapper.emitted('update:modelValue')
    expect(events).toBeTruthy()
    const lastPayload = events![events!.length - 1][0] as any
    expect(lastPayload.customFields[0].type).toBe('number')
  })

  it('shows options textarea for select type', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Color', type: 'select', required: false, orderIndex: 0, optionsText: 'Red\nBlue' }],
        products: [],
      },
    })

    const textarea = wrapper.find('textarea')
    expect(textarea.exists()).toBe(true)
  })

  it('hides options textarea for non-select types', () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Weight', type: 'number', required: false, orderIndex: 0 }],
        products: [],
      },
    })

    const textarea = wrapper.find('textarea')
    expect(textarea.exists()).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// Required checkbox
// ---------------------------------------------------------------------------
describe('required checkbox', () => {
  it('toggles required on checkbox change', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Test', type: 'text', required: false, orderIndex: 0 }],
        products: [],
      },
    })

    const checkbox = wrapper.find('input[type="checkbox"]')
    await checkbox.setValue(true)
    await nextTick()

    const events = wrapper.emitted('update:modelValue')
    expect(events).toBeTruthy()
    const lastPayload = events![events!.length - 1][0] as any
    expect(lastPayload.customFields[0].required).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// Add product
// ---------------------------------------------------------------------------
describe('add product', () => {
  it('adds a product entry on button click', async () => {
    const wrapper = mountEditor()
    await getAddProductButton(wrapper).trigger('click')
    await nextTick()

    expect(wrapper.text()).not.toContain('Aucun produit défini')
    // Should have a product type select
    expect(wrapper.findAll('select').length).toBeGreaterThanOrEqual(1)
  })
})

// ---------------------------------------------------------------------------
// Options parsing
// ---------------------------------------------------------------------------
describe('options parsing', () => {
  it('parses multiline options text into array', async () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [{ name: 'Color', type: 'select', required: false, orderIndex: 0, optionsText: '' }],
        products: [],
      },
    })

    const textarea = wrapper.find('textarea')
    await textarea.setValue('Red\nGreen\nBlue')
    await nextTick()

    const events = wrapper.emitted('update:modelValue')
    expect(events).toBeTruthy()
    const lastPayload = events![events!.length - 1][0] as any
    expect(lastPayload.customFields[0].options).toEqual(['Red', 'Green', 'Blue'])
  })
})

// ---------------------------------------------------------------------------
// Hydration from modelValue
// ---------------------------------------------------------------------------
describe('hydration', () => {
  it('renders existing fields from modelValue', () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [
          { name: 'Weight', type: 'number', required: true, orderIndex: 0 },
          { name: 'Color', type: 'text', required: false, orderIndex: 1 },
        ],
        products: [],
      },
    })

    const nameInputs = wrapper.findAll('input[type="text"]')
    expect(nameInputs.length).toBe(2)
    expect((nameInputs[0].element as HTMLInputElement).value).toBe('Weight')
    expect((nameInputs[1].element as HTMLInputElement).value).toBe('Color')
  })

  it('sorts fields by orderIndex', () => {
    const wrapper = mountEditor({
      modelValue: {
        customFields: [
          { name: 'Second', type: 'text', required: false, orderIndex: 1 },
          { name: 'First', type: 'text', required: false, orderIndex: 0 },
        ],
        products: [],
      },
    })

    const nameInputs = wrapper.findAll('input[type="text"]')
    expect((nameInputs[0].element as HTMLInputElement).value).toBe('First')
    expect((nameInputs[1].element as HTMLInputElement).value).toBe('Second')
  })
})
