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

const updateAction = vi.fn()
const setDraftAction = vi.fn()
const clearDraftAction = vi.fn()

vi.mock('@/actions/user-settings', () => ({
  updateUserSettingsAction: (...args: unknown[]) => updateAction(...args),
}))

vi.mock('@/actions/sprint-draft', () => ({
  setPendingSprintDraftAction: (...args: unknown[]) => setDraftAction(...args),
  clearPendingSprintDraftAction: (...args: unknown[]) =>
    clearDraftAction(...args),
}))

import { useUserSettingsStore } from '@/stores/user-settings/store'
import type { PendingSprintDraft } from '@/lib/user-settings'

function resetStore() {
  useUserSettingsStore.setState((s) => {
    s.entities.settings = {}
    s.context.hydrated = false
    s.context.isDemo = false
    s.pendingMutations = {}
  })
}

beforeEach(() => {
  resetStore()
  updateAction.mockReset()
  setDraftAction.mockReset()
  clearDraftAction.mockReset()
})

afterEach(() => {
  resetStore()
})

describe('useUserSettingsStore', () => {
  it('hydrate sets entities and context', () => {
    useUserSettingsStore.getState().hydrate(
      { views: { sprintBacklog: { sort: 'code' } } },
      false,
    )
    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.views?.sprintBacklog?.sort).toBe('code')
    expect(s.context.hydrated).toBe(true)
    expect(s.context.isDemo).toBe(false)
  })

  it('setPref updates state optimistically and settles on success', async () => {
    useUserSettingsStore.getState().hydrate({}, false)
    updateAction.mockResolvedValueOnce({
      success: true,
      settings: { views: { sprintBacklog: { filterStatus: 'all' } } },
    })

    await useUserSettingsStore
      .getState()
      .setPref(['views', 'sprintBacklog', 'filterStatus'], 'all')

    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.views?.sprintBacklog?.filterStatus).toBe('all')
    expect(Object.keys(s.pendingMutations)).toHaveLength(0)
    expect(updateAction).toHaveBeenCalledWith({
      views: { sprintBacklog: { filterStatus: 'all' } },
    })
  })

  it('setPref rolls back on server error', async () => {
    useUserSettingsStore.getState().hydrate(
      { views: { sprintBacklog: { sort: 'code' } } },
      false,
    )
    updateAction.mockResolvedValueOnce({ error: 'boom', code: 422 })

    await useUserSettingsStore
      .getState()
      .setPref(['views', 'sprintBacklog', 'sort'], 'priority')

    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.views?.sprintBacklog?.sort).toBe('code')
    expect(Object.keys(s.pendingMutations)).toHaveLength(0)
  })

  it('setPref skips server-call for demo accounts', async () => {
    useUserSettingsStore.getState().hydrate({}, true)

    await useUserSettingsStore
      .getState()
      .setPref(['devTools', 'debugMode'], true)

    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.devTools?.debugMode).toBe(true)
    expect(updateAction).not.toHaveBeenCalled()
  })

  it('setPendingSprintDraft persists draft lokaal (session-only, geen server-call)', async () => {
    useUserSettingsStore.getState().hydrate({}, false)

    const draft: PendingSprintDraft = {
      goal: 'Sprint 1',
      pbiIntent: { pbiA: 'all' },
      storyOverrides: {},
    }
    await useUserSettingsStore
      .getState()
      .setPendingSprintDraft('product-1', draft)

    const s = useUserSettingsStore.getState()
    expect(
      s.entities.settings.workflow?.pendingSprintDraft?.['product-1'],
    ).toMatchObject({ goal: 'Sprint 1' })
    expect(setDraftAction).not.toHaveBeenCalled()
  })

  it('hydrate strips workflow.pendingSprintDraft uit legacy server-state', () => {
    useUserSettingsStore.getState().hydrate(
      {
        workflow: {
          pendingSprintDraft: {
            'product-1': {
              goal: 'Legacy draft',
              pbiIntent: {},
              storyOverrides: {},
            },
          },
        },
      },
      false,
    )

    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.workflow?.pendingSprintDraft).toBeUndefined()
  })

  it('clearPendingSprintDraft verwijdert de key lokaal zonder server-call', async () => {
    useUserSettingsStore.getState().hydrate({}, false)
    await useUserSettingsStore.getState().setPendingSprintDraft('product-1', {
      goal: 'Old',
      pbiIntent: {},
      storyOverrides: {},
    })

    await useUserSettingsStore
      .getState()
      .clearPendingSprintDraft('product-1')

    const s = useUserSettingsStore.getState()
    expect(
      s.entities.settings.workflow?.pendingSprintDraft?.['product-1'],
    ).toBeUndefined()
    expect(clearDraftAction).not.toHaveBeenCalled()
  })

  it('upsertPbiIntent updates intent and wipes storyOverrides for that PBI', async () => {
    useUserSettingsStore.getState().hydrate({}, false)
    await useUserSettingsStore.getState().setPendingSprintDraft('product-1', {
      goal: 'g',
      pbiIntent: { pbiA: 'none' },
      storyOverrides: {
        pbiA: { add: ['s-1'], remove: [] },
        pbiB: { add: [], remove: ['s-2'] },
      },
    })

    await useUserSettingsStore
      .getState()
      .upsertPbiIntent('product-1', 'pbiA', 'all')

    const draft =
      useUserSettingsStore.getState().entities.settings.workflow
        ?.pendingSprintDraft?.['product-1']
    expect(draft?.pbiIntent.pbiA).toBe('all')
    expect(draft?.storyOverrides.pbiA).toBeUndefined()
    expect(draft?.storyOverrides.pbiB).toEqual({ add: [], remove: ['s-2'] })
  })

  it('upsertStoryOverride add adds to add[] and removes from remove[]', async () => {
    useUserSettingsStore.getState().hydrate({}, false)
    await useUserSettingsStore.getState().setPendingSprintDraft('product-1', {
      goal: 'g',
      pbiIntent: {},
      storyOverrides: {
        pbiA: { add: [], remove: ['story-1'] },
      },
    })

    await useUserSettingsStore
      .getState()
      .upsertStoryOverride('product-1', 'pbiA', 'story-1', 'add')

    const draft =
      useUserSettingsStore.getState().entities.settings.workflow
        ?.pendingSprintDraft?.['product-1']
    expect(draft?.storyOverrides.pbiA).toEqual({
      add: ['story-1'],
      remove: [],
    })
  })

  it('upsertStoryOverride clear removes from both arrays and drops empty entry', async () => {
    useUserSettingsStore.getState().hydrate({}, false)
    await useUserSettingsStore.getState().setPendingSprintDraft('product-1', {
      goal: 'g',
      pbiIntent: {},
      storyOverrides: {
        pbiA: { add: ['story-1'], remove: [] },
      },
    })

    await useUserSettingsStore
      .getState()
      .upsertStoryOverride('product-1', 'pbiA', 'story-1', 'clear')

    const draft =
      useUserSettingsStore.getState().entities.settings.workflow
        ?.pendingSprintDraft?.['product-1']
    expect(draft?.storyOverrides.pbiA).toBeUndefined()
  })

  it('applyServerPatch merges without optimistic state', () => {
    useUserSettingsStore.getState().hydrate(
      { views: { sprintBacklog: { sort: 'code' } } },
      false,
    )

    useUserSettingsStore.getState().applyServerPatch({
      views: { sprintBacklog: { sortDir: 'desc' } },
    })

    const s = useUserSettingsStore.getState()
    expect(s.entities.settings.views?.sprintBacklog).toEqual({
      sort: 'code',
      sortDir: 'desc',
    })
    expect(Object.keys(s.pendingMutations)).toHaveLength(0)
  })
})
