import type { ReactNode } from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import type { DashboardStats } from '@/types/stats'
import { DashboardPage } from './DashboardPage'

const getDashboardStatsMock = vi.fn<() => Promise<DashboardStats>>()
const getErrorMessageMock = vi.fn<(error: unknown, fallback: string) => string>()

function flattenChildren(children: ReactNode): string {
  if (children === null || children === undefined || typeof children === 'boolean') {
    return ''
  }

  if (typeof children === 'string' || typeof children === 'number') {
    return String(children)
  }

  if (Array.isArray(children)) {
    return children.map(flattenChildren).join(' ').trim()
  }

  if (typeof children === 'object' && 'props' in children) {
    return flattenChildren((children as { props?: { children?: ReactNode } }).props?.children)
  }

  return ''
}

function createDeferred<T>() {
  let resolve!: (value: T) => void
  let reject!: (error?: unknown) => void

  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })

  return { promise, resolve, reject }
}

const sampleStats: DashboardStats = {
  users: {
    total_users: 101,
    active_users: 102,
    inactive_users: 103,
    locked_users: 104,
    disabled_users: 105,
    new_users_today: 106,
    new_users_week: 107,
    new_users_month: 108,
  },
  logins: {
    logins_today_success: 109,
    logins_today_failed: 110,
    logins_week: 111,
  },
}

vi.mock('antd', () => ({
  Col: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  Row: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  Statistic: ({
    title,
    value,
  }: {
    title?: ReactNode
    value?: ReactNode
  }) => (
    <div data-testid="statistic">
      <span>{flattenChildren(title)}</span>
      <span>{String(value)}</span>
    </div>
  ),
  Tooltip: ({
    children,
    title,
  }: {
    children?: ReactNode
    title?: ReactNode
  }) => <span data-tooltip={flattenChildren(title)}>{children}</span>,
  Typography: {
    Text: ({
      children,
      className,
      type,
    }: {
      children?: ReactNode
      className?: string
      type?: string
    }) => {
      void className
      void type
      return <span>{children}</span>
    },
  },
}))

vi.mock('@ant-design/icons', () => ({
  CloseCircleOutlined: () => <span>close-icon</span>,
  InfoCircleOutlined: () => <span>info-icon</span>,
  LockOutlined: () => <span>lock-icon</span>,
  LoginOutlined: () => <span>login-icon</span>,
  StopOutlined: () => <span>stop-icon</span>,
  TeamOutlined: () => <span>team-icon</span>,
  UserAddOutlined: () => <span>user-add-icon</span>,
  UserOutlined: () => <span>user-icon</span>,
}))

vi.mock('@/components/common', () => ({
  PageHeader: ({
    title,
    description,
  }: {
    title: ReactNode
    description?: ReactNode
  }) => (
    <section data-testid="page-header">
      <h1>{flattenChildren(title)}</h1>
      <p>{flattenChildren(description)}</p>
    </section>
  ),
}))

vi.mock('@/components/feedback', () => ({
  PageLoading: ({ tip }: { tip?: ReactNode }) => (
    <div data-testid="page-loading">{flattenChildren(tip)}</div>
  ),
  PageError: ({
    description,
    onRetry,
  }: {
    description?: ReactNode
    onRetry?: () => void
  }) => (
    <div data-testid="page-error" data-has-retry={String(Boolean(onRetry))}>
      <span>{flattenChildren(description)}</span>
      {onRetry ? (
        <button type="button" onClick={onRetry}>
          retry
        </button>
      ) : null}
    </div>
  ),
}))

vi.mock('@/components/layout', () => ({
  ContentCard: ({ children }: { children?: ReactNode }) => (
    <div data-testid="content-card">{children}</div>
  ),
  PageLayout: ({ children }: { children?: ReactNode }) => (
    <div data-testid="page-layout">{children}</div>
  ),
}))

vi.mock('@/lib/errors', () => ({
  getErrorMessage: (error: unknown, fallback: string) => getErrorMessageMock(error, fallback),
}))

vi.mock('@/services/stats', () => ({
  getDashboardStats: () => getDashboardStatsMock(),
}))

describe('DashboardPage', () => {
  beforeEach(() => {
    getDashboardStatsMock.mockReset()
    getErrorMessageMock.mockReset()
    getErrorMessageMock.mockImplementation((_, fallback) => fallback)
  })

  it('shows loading first and then renders the dashboard cards after stats load', async () => {
    const deferred = createDeferred<DashboardStats>()
    getDashboardStatsMock.mockReturnValueOnce(deferred.promise)

    render(<DashboardPage />)

    expect(screen.getByTestId('page-loading')).toBeInTheDocument()
    expect(getDashboardStatsMock).toHaveBeenCalledTimes(1)

    deferred.resolve(sampleStats)

    expect(await screen.findByTestId('page-layout')).toBeInTheDocument()
    expect(screen.getByTestId('page-header')).toBeInTheDocument()
    expect(screen.getAllByTestId('content-card')).toHaveLength(12)

    const userValues = Object.values(sampleStats.users)
    const loginValues = Object.values(sampleStats.logins)
    for (const value of [...userValues, ...loginValues]) {
      expect(screen.getByText(String(value))).toBeInTheDocument()
    }
  })

  it('renders a retriable error state and reloads data successfully on retry', async () => {
    const user = userEvent.setup()

    getDashboardStatsMock
      .mockRejectedValueOnce(new Error('network down'))
      .mockResolvedValueOnce(sampleStats)
    getErrorMessageMock.mockReturnValue('dashboard load failed')

    render(<DashboardPage />)

    expect(await screen.findByTestId('page-error')).toHaveAttribute('data-has-retry', 'true')
    expect(screen.getByText('dashboard load failed')).toBeInTheDocument()
    expect(getErrorMessageMock).toHaveBeenCalledWith(expect.any(Error), expect.any(String))

    await user.click(screen.getByRole('button', { name: 'retry' }))

    expect(await screen.findByTestId('page-layout')).toBeInTheDocument()
    expect(getDashboardStatsMock).toHaveBeenCalledTimes(2)
  })

  it('renders a non-retriable empty-state error when the stats payload is missing', async () => {
    getDashboardStatsMock.mockResolvedValueOnce(null as unknown as DashboardStats)

    render(<DashboardPage />)

    expect(await screen.findByTestId('page-error')).toHaveAttribute('data-has-retry', 'false')
    expect(screen.queryByRole('button', { name: 'retry' })).not.toBeInTheDocument()
    expect(screen.queryByTestId('page-layout')).not.toBeInTheDocument()
  })
})
