import { describe, expect, it } from 'vitest'

import { creatableImportRows,
	 buildNextEditedRow,
	 canEditResultRow,
	 canEditReviewRow,
	 canRetryResultRow,
	 hasExactSourceRows,
	 initialisePreviewRows,
	 mergeImportResults,
	 mergeValidatedImportRow,
	 mergeValidatedImportRows,
	 processableImportRows,
	 replaceImportRow,
	 resultRepairMode,
	 resultRowMessages,
	 resultSummaryCounts,
	 retryImportRow,
	 reviewSummaryCounts } from '@/lib/postImportRows'
import { buildPostImportRow } from '@/test/postImportFactories'

describe ('post import row state', () => {
  it ('separates processable existing rows from creatable rows', () => {
    const ready = buildPostImportRow ({ sourceRow: 1 })
    const existing = buildPostImportRow ({
      sourceRow: 2,
      skipReason: 'existing',
      existingPostId: 20 })
    const manual = buildPostImportRow ({
      sourceRow: 3,
      skipReason: 'manual' })
    const invalid = buildPostImportRow ({
      sourceRow: 4,
      status: 'error',
      validationErrors: { url: ['invalid'] } })
    const created = buildPostImportRow ({
      sourceRow: 5,
      importStatus: 'created',
      createdPostId: 40 })
    const rows = [ready, existing, manual, invalid, created]

    expect (processableImportRows (rows)).toEqual ([ready])
    expect (creatableImportRows (rows)).toEqual ([ready])
    expect (reviewSummaryCounts (rows)).toEqual ({
      creatable: 1,
      manualSkipped: 1,
      existingSkipped: 1,
      pendingOrError: 1 })
  })

  it ('preserves terminal rows while merging validation results', () => {
    const created = buildPostImportRow ({
      sourceRow: 1,
      importStatus: 'created',
      createdPostId: 10,
      attributes: { title: 'created title' } })
    const pending = buildPostImportRow ({
      sourceRow: 2,
      fieldWarnings: { title: ['old warning'] },
      provenance: { title: 'manual' },
      attributes: { title: 'manual title' } })
    const validated = [
      buildPostImportRow ({ sourceRow: 1, attributes: { title: 'changed' } }),
      buildPostImportRow ({
	sourceRow: 2,
	fieldWarnings: { title: ['fetch warning'], tags: ['tag warning'] },
	provenance: { title: 'manual' },
	attributes: { title: 'manual title' } })]

    const result = mergeValidatedImportRows ([created, pending], validated)

    expect (result[0]).toBe (created)
    expect (result[1]?.fieldWarnings).toEqual ({ tags: ['tag warning'] })
  })

  it ('updates the reset snapshot only after metadata URL changes', () => {
    const current = buildPostImportRow ({
      attributes: { title: 'manual title' },
      metadataUrl: 'https://example.com/old' })
    const validated = buildPostImportRow ({
      attributes: { title: 'new metadata title' },
      metadataUrl: 'https://example.com/new',
      fieldWarnings: { title: ['warning'] },
      baseWarnings: ['base warning'] })

    const result = mergeValidatedImportRows ([current], [validated])[0]

    expect (result?.resetSnapshot).toMatchObject ({
      attributes: { title: 'new metadata title' },
      fieldWarnings: { title: ['warning'] },
      baseWarnings: ['base warning'],
      metadataUrl: 'https://example.com/new' })
  })

  it ('merges result states and clears incompatible post identifiers', () => {
    const created = mergeImportResults ([buildPostImportRow ({
      skipReason: 'existing',
      existingPostId: 2,
      recoverable: true,
      importStatus: 'pending' })], [{
      sourceRow: 1,
      status: 'created',
      post: { id: 3 } }])[0]
    const skipped = mergeImportResults ([buildPostImportRow ({
      createdPostId: 3,
      importStatus: 'created',
      recoverable: true })], [{
      sourceRow: 1,
      status: 'skipped',
      existingPostId: 4 }])[0]
    const failed = mergeImportResults ([buildPostImportRow ({
      skipReason: 'existing',
      existingPostId: 4 })], [{
      sourceRow: 1,
      status: 'failed',
      recoverable: true,
      errors: { base: ['failure'] } }])[0]

    expect (created).toMatchObject ({
      importStatus: 'created',
      createdPostId: 3,
      existingPostId: undefined,
      recoverable: undefined,
      skipReason: undefined })
    expect (skipped).toMatchObject ({
      importStatus: 'skipped',
      existingPostId: 4,
      createdPostId: undefined,
      recoverable: undefined,
      skipReason: 'existing' })
    expect (failed).toMatchObject ({
      importStatus: 'failed',
      recoverable: true,
      createdPostId: undefined,
      existingPostId: undefined,
      skipReason: undefined,
      importErrors: { base: ['failure'] } })
  })

  it ('retries only the selected failed row and counts results exclusively', () => {
    const rows = [
      buildPostImportRow ({ sourceRow: 1, importStatus: 'created', createdPostId: 1 }),
      buildPostImportRow ({ sourceRow: 2, importStatus: 'skipped',
	existingPostId: 2, skipReason: 'existing' }),
      buildPostImportRow ({ sourceRow: 3, importStatus: 'failed',
	recoverable: true, importErrors: { base: ['failed'] } }),
      buildPostImportRow ({ sourceRow: 4, importStatus: 'pending',
	recoverable: true, validationErrors: { base: ['failed'] } })]

    expect (resultSummaryCounts (rows)).toEqual ({ created: 1, skipped: 1, failed: 2 })
    expect (retryImportRow (rows, 3)[2]).toMatchObject ({
      importStatus: 'pending',
      importErrors: undefined })
    expect (retryImportRow (rows, 4)[3]).toBe (rows[3])
  })

  it ('deduplicates messages and keeps repair mode only for repairable rows', () => {
    const repairable = buildPostImportRow ({
      sourceRow: 1,
      importStatus: 'pending',
      validationErrors: { title: ['invalid'], base: ['duplicate'] },
      importErrors: { base: ['duplicate'], url: ['network'] } })
    const complete = buildPostImportRow ({
      sourceRow: 2,
      importStatus: 'created',
      createdPostId: 2 })

    expect (resultRowMessages (repairable)).toEqual (['invalid', 'duplicate', 'network'])
    expect (resultRepairMode ([repairable, complete])).toBe ('failed')
    expect (resultRepairMode ([complete])).toBe ('all')
  })

  it ('classifies editable and retryable rows by terminal and recoverable state', () => {
    const ready = buildPostImportRow ()
    const skipped = buildPostImportRow ({
      importStatus: 'skipped',
      skipReason: 'existing',
      existingPostId: 2 })
    const hardFailed = buildPostImportRow ({
      importStatus: 'failed',
      importErrors: { base: ['failed'] } })
    const pendingInvalid = buildPostImportRow ({
      importStatus: 'pending',
      recoverable: true,
      validationErrors: { title: ['invalid'] } })
    const pendingValid = buildPostImportRow ({
      importStatus: 'pending',
      recoverable: true })

    expect (canEditReviewRow (ready)).toBe (true)
    expect (canEditReviewRow (skipped)).toBe (false)
    expect (canEditReviewRow (hardFailed)).toBe (false)
    expect (canEditResultRow (pendingInvalid)).toBe (true)
    expect (canRetryResultRow (pendingInvalid)).toBe (false)
    expect (canRetryResultRow (pendingValid)).toBe (true)
  })

  it ('detects missing, duplicate, and extra source rows exactly', () => {
    expect (hasExactSourceRows ([1, 2], [{ sourceRow: 1 }, { sourceRow: 2 }])).toBe (true)
    expect (hasExactSourceRows ([1, 2], [{ sourceRow: 1 }])).toBe (false)
    expect (hasExactSourceRows ([1, 2], [{ sourceRow: 1 }, { sourceRow: 1 }])).toBe (false)
    expect (hasExactSourceRows ([1, 2], [{ sourceRow: 1 }, { sourceRow: 3 }])).toBe (false)
  })

  it ('merges only the validated source row and preserves other row edits', () => {
    const edited = buildPostImportRow ({
      sourceRow: 1,
      attributes: { title: 'edited row' },
      provenance: { title: 'manual' } })
    const other = buildPostImportRow ({
      sourceRow: 2,
      attributes: { title: 'keep me' },
      provenance: { title: 'manual' } })
    const validated = buildPostImportRow ({
      sourceRow: 1,
      attributes: { title: 'validated row' } })

    const result = mergeValidatedImportRow ([edited, other], validated)

    expect (result[0]?.attributes.title).toBe ('validated row')
    expect (result[1]?.attributes.title).toBe ('keep me')
  })

  it ('replaces only the targeted source row and preserves the others', () => {
    const original = buildPostImportRow ({
      sourceRow: 1,
      importStatus: 'failed',
      recoverable: true,
      importErrors: { base: ['failed'] } })
    const other = buildPostImportRow ({
      sourceRow: 2,
      attributes: { title: 'keep edited row' } })
    const restored = buildPostImportRow ({
      sourceRow: 1,
      importStatus: 'failed',
      recoverable: true,
      importErrors: { base: ['failed'] } })

    const result = replaceImportRow ([original, other], restored)

    expect (result[0]).toEqual (restored)
    expect (result[1]?.attributes.title).toBe ('keep edited row')
  })

  it ('copies reset snapshot values instead of sharing mutable records', () => {
    const row = buildPostImportRow ({ fieldWarnings: { title: ['warning'] } })
    const initialised = initialisePreviewRows ([row])[0]
    expect (initialised).toBeDefined ()
    if (initialised == null)
      return

    initialised.attributes.title = 'changed'
    initialised.fieldWarnings.title?.push ('another')

    expect (initialised.resetSnapshot.attributes.title).toBe ('')
    expect (initialised.resetSnapshot.fieldWarnings.title).toEqual (['warning'])
  })

  it ('builds the next edited row with shared repair semantics', () => {
    const row = buildPostImportRow ({
      url: 'https://example.com/original',
      importStatus: 'failed',
      recoverable: true,
      importErrors: { base: ['failed'] },
      attributes: { title: 'old title', tags: 'old-tag', duration: '2' },
      provenance: { title: 'automatic', tags: 'automatic', url: 'manual' },
      tagSources: { automatic: 'old-tag', manual: '' } })

    const nextRow = buildNextEditedRow (
        row,
        {
          url: 'https://example.com/edited',
          title: 'edited title',
          thumbnailBase: '',
          originalCreatedFrom: '',
          originalCreatedBefore: '',
          duration: '2',
          tags: 'edited-tag',
          parentPostIds: '' },
        true)

    expect (nextRow.importStatus).toBe ('pending')
    expect (nextRow.importErrors).toBeUndefined ()
    expect (nextRow.url).toBe ('https://example.com/edited')
    expect (nextRow.attributes.title).toBe ('edited title')
    expect (nextRow.attributes.duration).toBe ('2')
    expect (nextRow.attributes.tags).toBe ('edited-tag')
    expect (nextRow.provenance.title).toBe ('manual')
    expect (nextRow.provenance.url).toBe ('manual')
    expect (nextRow.tagSources?.manual).toBe ('edited-tag')
  })
})
