// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'

// The Updates tab reads `/updates/check`, saves GitHub config via
// PUT `/updates/config`, and opens an upgrade PR via POST `/updates/apply`.
let checkData: any

const cmsApi = vi.fn(async (endpoint: string, options?: { method?: string; body?: unknown }) => {
  const method = options?.method ?? 'GET'
  if (endpoint === '/updates/check' && method === 'GET') return { data: checkData, status: 200 }
  if (endpoint === '/updates/config' && method === 'PUT')
    return { data: { success: true }, status: 200 }
  if (endpoint === '/updates/apply' && method === 'POST')
    return { data: { prUrl: 'https://github.com/x/y/pull/1', prNumber: 1 }, status: 200 }
  return { data: {}, status: 200 }
})

vi.mock('../../lib/api.js', () => ({ cmsApi: (e: string, o?: unknown) => cmsApi(e, o as any) }))
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn() } }))

const { UpdatesTab } = await import('../../views/settings/UpdatesTab.js')

function upToDate(overrides: Record<string, unknown> = {}) {
  return {
    current: '0.46.0',
    latest: '0.46.0',
    updateAvailable: false,
    canManage: true,
    hasGithubToken: false,
    githubRepo: '',
    source: 'update-server',
    ...overrides,
  }
}

function updateAvailable(overrides: Record<string, unknown> = {}) {
  return {
    current: '0.45.0',
    latest: '0.46.0',
    updateAvailable: true,
    severity: 'minor',
    releaseDate: '2026-01-02',
    changelog: [
      {
        version: '0.46.0',
        date: '2026-01-02',
        summary: 'Add Brand Voice training',
        type: 'feature',
      },
    ],
    hasMigrations: true,
    breakingChanges: ['Removed the legacy provider API'],
    pluginCompatibility: [
      { name: '@actuate-media/plugin-seo', compatible: true, minVersion: '0.46.0' },
    ],
    updateCommand: 'npm update @actuate-media/cms-core',
    canManage: true,
    hasGithubToken: false,
    githubRepo: '',
    source: 'update-server',
    ...overrides,
  }
}

beforeEach(() => {
  cmsApi.mockClear()
  checkData = upToDate()
})

describe('UpdatesTab', () => {
  it('shows the current version and an up-to-date banner', async () => {
    render(<UpdatesTab role="ADMIN" />)
    await screen.findByText("You're up to date!")
    expect(screen.getAllByText('0.46.0').length).toBeGreaterThan(0)
    // Admins see the GitHub integration card.
    expect(screen.getByText('GitHub Integration')).toBeTruthy()
  })

  it('surfaces severity, real changelog, migrations and breaking changes', async () => {
    checkData = updateAvailable()
    render(<UpdatesTab role="ADMIN" />)

    await screen.findByText('Update available')
    expect(screen.getByText('Minor')).toBeTruthy()
    expect(screen.getByText('Add Brand Voice training')).toBeTruthy()
    expect(screen.getByText('Database migrations required')).toBeTruthy()
    expect(screen.getByText('Breaking changes in this release')).toBeTruthy()
    expect(screen.getByText('Removed the legacy provider API')).toBeTruthy()
    // Plugin compatibility surfaced.
    expect(screen.getByText('plugin-seo')).toBeTruthy()
    expect(screen.getByRole('button', { name: 'Create Update PR' })).toBeTruthy()
  })

  it('flags a security release prominently', async () => {
    checkData = updateAvailable({ severity: 'security' })
    render(<UpdatesTab role="ADMIN" />)
    await screen.findByText('Security update available')
    expect(screen.getByText('Security')).toBeTruthy()
  })

  it('is permission-aware: non-admins cannot manage updates', async () => {
    checkData = updateAvailable({ canManage: false })
    render(<UpdatesTab role="EDITOR" />)

    await screen.findByText('Update available')
    expect(screen.queryByText('GitHub Integration')).toBeNull()
    expect(screen.queryByRole('button', { name: 'Create Update PR' })).toBeNull()
    expect(screen.getByText(/Ask an administrator/i)).toBeTruthy()
  })

  it('enables Save when the repo changes and persists via PUT /updates/config', async () => {
    render(<UpdatesTab role="ADMIN" />)
    await screen.findByText('GitHub Integration')

    const save = screen.getByRole('button', { name: 'Save Changes' }) as HTMLButtonElement
    expect(save.disabled).toBe(true)

    const repo = screen.getByLabelText('Repository') as HTMLInputElement
    fireEvent.change(repo, { target: { value: 'actuate-media/site' } })
    expect(save.disabled).toBe(false)

    fireEvent.click(save)
    await waitFor(() =>
      expect(cmsApi).toHaveBeenCalledWith(
        '/updates/config',
        expect.objectContaining({
          method: 'PUT',
          body: expect.stringContaining('actuate-media/site'),
        }),
      ),
    )
  })

  it('blocks Save on an invalid repository format', async () => {
    render(<UpdatesTab role="ADMIN" />)
    await screen.findByText('GitHub Integration')

    const repo = screen.getByLabelText('Repository') as HTMLInputElement
    fireEvent.change(repo, { target: { value: 'not a repo' } })

    expect(screen.getByText(/owner\/repo format/i)).toBeTruthy()
    const save = screen.getByRole('button', { name: 'Save Changes' }) as HTMLButtonElement
    expect(save.disabled).toBe(true)
  })

  it('creates an update PR via POST /updates/apply', async () => {
    checkData = updateAvailable()
    render(<UpdatesTab role="ADMIN" />)
    await screen.findByText('Update available')

    fireEvent.click(screen.getByRole('button', { name: 'Create Update PR' }))
    await waitFor(() =>
      expect(cmsApi).toHaveBeenCalledWith(
        '/updates/apply',
        expect.objectContaining({ method: 'POST', body: expect.stringContaining('0.46.0') }),
      ),
    )
    await screen.findByText('Pull Request Created')
  })
})
