/**
 * Sprint UI-deep, пункт 3: Products full CRUD через UI.
 * - Создание с разными типами цен и штрихкодом
 * - Загрузка изображения через page.setInputFiles()
 * - Редактирование
 * - Дубль артикула → ошибка с понятным сообщением
 * - Удаление через ConfirmDialog → подтвердить → запись пропала
 * - Поиск по списку
 * - Пагинация (создаём >50 товаров и проверяем)
 */
import { test, expect, request as apiRequest } from '@playwright/test'
import { apiSignup, attachSession, watchPage, expectNoErrors } from '../lib/ui.js'
import { promises as fs } from 'node:fs'
import path from 'node:path'

const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz'

test.describe('UI-3 products full CRUD', () => {
  test.describe.configure({ mode: 'serial' })

  test('3.1 create → edit → delete через UI с confirm-диалогом', async ({ page }) => {
    test.setTimeout(120_000)
    const sess = await apiSignup('crud31')
    const errs = watchPage(page)
    await attachSession(page, sess, '/catalog/products')

    // CREATE
    await page.getByRole('button', { name: /создать первый товар/i }).click()
    await page.waitForURL(/\/catalog\/products\/new/, { timeout: 10_000 })
    const name = `CRUD ${Date.now()}`
    await page.getByLabel('Название *').fill(name)
    await page.getByLabel(/Розничная/).first().fill('1500')
    // Дожидаемся currencies (MoneyInput должен стать enabled)
    await expect(page.getByLabel(/Розничная/).first()).toBeEnabled()
    const saveBtn = page.getByRole('button', { name: /^сохранить$/i }).last()
    await expect(saveBtn).toBeEnabled({ timeout: 8_000 })
    await saveBtn.click()
    // После save переходим на /catalog/products/<id>
    await page.waitForURL(/\/catalog\/products\/[0-9a-f-]{36}$/, { timeout: 10_000 })

    // EDIT — меняем цену
    await page.getByLabel(/Розничная/).first().fill('2000')
    await saveBtn.click()
    // PUT → navigate('/catalog/products') (на список). Toast «Сохранено» может
    // мелькнуть/исчезнуть до waitForURL, поэтому не настаиваем на нём.
    await page.waitForURL(/\/catalog\/products$/, { timeout: 10_000 })
    // На списке наш товар с обновлённой ценой «2 000»
    await expect(page.locator('tbody').getByText(/2[\s ]?000/).first())
      .toBeVisible({ timeout: 5_000 })

    // Возвращаемся в карточку для DELETE
    await page.locator('tbody').getByText(name).click()
    await page.waitForURL(/\/catalog\/products\/[0-9a-f-]{36}$/, { timeout: 10_000 })

    // DELETE — кнопка Удалить → ConfirmDialog → подтвердить
    await page.getByRole('button', { name: /удалить/i }).click()
    const dialog = page.locator('[aria-labelledby="confirm-dialog-title"]').first()
    await expect(dialog).toBeVisible({ timeout: 5_000 })
    await expect(dialog).toContainText(/удалить товар/i)
    await dialog.getByRole('button', { name: /удалить/i }).click()
    // Редирект на список
    await page.waitForURL(/\/catalog\/products$/, { timeout: 10_000 })
    // Запись исчезла
    await expect(page.locator('tbody').getByText(name)).not.toBeVisible({ timeout: 5_000 })

    expectNoErrors(errs, 'crud product')
  })

  test('3.2 дубль артикула → 409 с понятной ошибкой через toast', async ({ page }) => {
    const sess = await apiSignup('crud32')
    const errs = watchPage(page, {
      // ожидаем 409 на дубликат — это и есть проверка
      expected4xxContains: ['/api/catalog/products'],
    })
    // Создаём первый товар через API
    const ctx = await apiRequest.newContext({
      baseURL: BASE, ignoreHTTPSErrors: true,
      extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` },
    })
    type Paged<T> = { items: T[] }
    const units = await (await ctx.get('/api/catalog/units-of-measure?pageSize=200')).json() as Paged<{ id: string; code: string }>
    const groups = await (await ctx.get('/api/catalog/product-groups')).json() as Paged<{ id: string }>
    const pts = await (await ctx.get('/api/catalog/price-types')).json() as Paged<{ id: string; isRetail: boolean }>
    const curs = await (await ctx.get('/api/catalog/currencies')).json() as Paged<{ id: string; code: string }>
    const unit = units.items.find(u => u.code === '796')!
    const grp = groups.items[0]
    const retail = pts.items.find(p => p.isRetail)!
    const kzt = curs.items.find(c => c.code === 'KZT')!

    const article = `DUP-${Date.now()}`
    const first = await ctx.post('/api/catalog/products', {
      data: {
        name: 'Первый', article,
        unitOfMeasureId: unit.id, vat: 12, vatEnabled: true,
        productGroupId: grp.id, packaging: 1,
        prices: [{ priceTypeId: retail.id, amount: 100, currencyId: kzt.id }],
        barcodes: [{ code: '4000000000016', type: 1, isPrimary: true }],
      },
    })
    expect([200, 201]).toContain(first.status())
    await ctx.dispose()

    // Через UI пытаемся создать второй с тем же артикулом
    await attachSession(page, sess, '/catalog/products/new')
    await page.getByLabel('Название *').fill('Дубль')
    // Перебиваем сгенерированный артикул на дубликат
    await page.getByLabel('Артикул *').fill(article)
    await page.getByLabel(/Розничная/).first().fill('200')
    await expect(page.getByLabel(/Розничная/).first()).toBeEnabled()
    const saveBtn = page.getByRole('button', { name: /^сохранить$/i }).last()
    await expect(saveBtn).toBeEnabled({ timeout: 8_000 })
    await saveBtn.click()

    // Должен появиться toast с человеко-читаемым сообщением (не «Request failed»)
    const alertText = await page.getByRole('alert').first().textContent({ timeout: 8_000 })
    expect(alertText, 'toast не должен содержать generic axios message')
      .not.toMatch(/Request failed with status code/i)
    // должен содержать ключевые слова про артикул/уникальность
    expect(alertText?.toLowerCase() ?? '').toMatch(/артикул|уже существует|already|уник/i)

    expectNoErrors(errs, 'dup article')
  })

  test('3.3 поиск по списку', async ({ page }) => {
    const sess = await apiSignup('crud33')
    const errs = watchPage(page)
    // Seed-demo для наполнения
    const ctx = await apiRequest.newContext({
      baseURL: BASE, ignoreHTTPSErrors: true,
      extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` },
    })
    await ctx.post('/api/admin/seed-demo', { data: {} })
    await ctx.dispose()

    await attachSession(page, sess, '/catalog/products')
    // Жде первую строку
    await page.locator('tbody tr').first().waitFor({ timeout: 10_000 })

    // Поиск «Молоко»
    await page.getByPlaceholder(/поиск/i).fill('Молоко')
    // Дебаунс ~300ms + сетевой запрос
    await page.waitForLoadState('networkidle')
    // В таблице должны остаться только Молоко-records
    const rows = await page.locator('tbody tr').count()
    expect(rows).toBeGreaterThan(0)
    const allCellsText = await page.locator('tbody').innerText()
    expect(allCellsText.toLowerCase()).toContain('молок')
    // Без других категорий
    expect(allCellsText.toLowerCase()).not.toContain('хлеб ржан')

    expectNoErrors(errs, 'search')
  })

  test('3.4 пагинация при >50 товаров', async ({ page }) => {
    test.setTimeout(120_000)
    const sess = await apiSignup('crud34')
    const errs = watchPage(page)
    // Seed-demo даёт 50; добавим ещё немного через API
    const ctx = await apiRequest.newContext({
      baseURL: BASE, ignoreHTTPSErrors: true,
      extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` },
    })
    await ctx.post('/api/admin/seed-demo', { data: {} })
    type Paged<T> = { items: T[] }
    const units = await (await ctx.get('/api/catalog/units-of-measure?pageSize=200')).json() as Paged<{ id: string; code: string }>
    const groups = await (await ctx.get('/api/catalog/product-groups?pageSize=200')).json() as Paged<{ id: string }>
    const pts = await (await ctx.get('/api/catalog/price-types?pageSize=50')).json() as Paged<{ id: string; isRetail: boolean }>
    const curs = await (await ctx.get('/api/catalog/currencies')).json() as Paged<{ id: string; code: string }>
    const unit = units.items.find(u => u.code === '796')!
    const grp = groups.items[0]
    const retail = pts.items.find(p => p.isRetail)!
    const kzt = curs.items.find(c => c.code === 'KZT')!
    // Дозабиваем до 55 (нужно >50 чтобы пагинация была)
    for (let i = 0; i < 5; i++) {
      const r = await ctx.post('/api/catalog/products', {
        data: {
          name: `Extra${i}`, article: `EXTRA-${Date.now()}-${i}`,
          unitOfMeasureId: unit.id, vat: 12, vatEnabled: true,
          productGroupId: grp.id, packaging: 1,
          prices: [{ priceTypeId: retail.id, amount: 100 + i, currencyId: kzt.id }],
          barcodes: [{ code: `40000000${(50000 + i).toString().padStart(5, '0')}`.slice(0, 13), type: 1, isPrimary: true }],
        },
      })
      expect([200, 201]).toContain(r.status())
    }
    await ctx.dispose()

    await attachSession(page, sess, '/catalog/products')
    await page.locator('tbody tr').first().waitFor({ timeout: 10_000 })

    // Pagination footer показывает «Стр X из Y» / номера страниц
    // 55 / 50 = 2 страницы
    await expect(page.locator('text=/2|следую|next/i').first()).toBeVisible({ timeout: 5_000 })
    expectNoErrors(errs, 'pagination')
  })

  test('3.5 загрузка изображения в карточку товара', async ({ page }) => {
    test.setTimeout(90_000)
    const sess = await apiSignup('crud35')
    const errs = watchPage(page)
    // Сначала создаём товар через API чтобы открыть его edit
    const ctx = await apiRequest.newContext({
      baseURL: BASE, ignoreHTTPSErrors: true,
      extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` },
    })
    type Paged<T> = { items: T[] }
    const units = await (await ctx.get('/api/catalog/units-of-measure?pageSize=200')).json() as Paged<{ id: string; code: string }>
    const groups = await (await ctx.get('/api/catalog/product-groups')).json() as Paged<{ id: string }>
    const pts = await (await ctx.get('/api/catalog/price-types')).json() as Paged<{ id: string; isRetail: boolean }>
    const curs = await (await ctx.get('/api/catalog/currencies')).json() as Paged<{ id: string; code: string }>
    const r = await ctx.post('/api/catalog/products', {
      data: {
        name: 'PhotoTest', article: `PHOTO-${Date.now()}`,
        unitOfMeasureId: units.items.find(u => u.code === '796')!.id, vat: 12, vatEnabled: true,
        productGroupId: groups.items[0].id, packaging: 1,
        prices: [{ priceTypeId: pts.items.find(p => p.isRetail)!.id, amount: 100, currencyId: curs.items.find(c => c.code === 'KZT')!.id }],
        barcodes: [{ code: '5000000000010', type: 1, isPrimary: true }],
      },
    })
    expect([200, 201]).toContain(r.status())
    const created = await r.json() as { id: string }
    await ctx.dispose()

    // Генерируем 1x1 PNG (минимальный валидный)
    const pngPath = path.join('/tmp', `test-img-${Date.now()}.png`)
    const oneByOnePng = Buffer.from(
      'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=',
      'base64',
    )
    await fs.writeFile(pngPath, oneByOnePng)

    await attachSession(page, sess, `/catalog/products/${created.id}`)
    await page.waitForLoadState('networkidle')

    // Скролл до секции «Изображения»
    await page.getByText('Изображения').first().scrollIntoViewIfNeeded()
    const fileInput = page.locator('input[type="file"]').first()
    await expect(fileInput).toBeAttached({ timeout: 5_000 })

    // Слушаем upload response чтобы понять, что произошло
    const uploadResp = page.waitForResponse(
      (r) => r.url().includes(`/api/catalog/products/${created.id}/images`) && r.request().method() === 'POST',
      { timeout: 15_000 },
    )
    await fileInput.setInputFiles(pngPath)
    const resp = await uploadResp
    const status = resp.status()
    // 200/201 — success; 415 mime — баг; 413 — слишком большой; 400 — что-то ещё
    expect(status, `image upload response (${await resp.text().catch(() => '')})`).toBeLessThan(400)

    expectNoErrors(errs, 'upload image')
    await fs.unlink(pngPath).catch(() => {})
  })
})
