/**
 * Integration test: App Card Operations Flow
 *
 * Tests the end-to-end user flows for app card operations:
 *   - Editing app info
 *   - Duplicating an app
 *   - Deleting an app
 *   - Exporting app DSL
 *   - Navigation on card click
 *   - Access mode icons
 */
import type { App } from '@/types/app'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import AppCard from '@/app/components/apps/app-card'
import { AccessMode } from '@/models/access-control'
import { exportAppConfig, updateAppInfo } from '@/service/apps'
import { AppModeEnum } from '@/types/app'

let mockIsCurrentWorkspaceEditor = true
let mockSystemFeatures = {
  branding: { enabled: false },
  webapp_auth: { enabled: false },
}

const toastMocks = vi.hoisted(() => ({
  mockNotify: vi.fn(),
  dismiss: vi.fn(),
  update: vi.fn(),
  promise: vi.fn(),
}))
const mockRouterPush = vi.fn()

vi.mock('@langgenius/dify-ui/toast', () => ({
  toast: {
    success: (message: string, options?: Record<string, unknown>) => toastMocks.mockNotify({ type: 'success', message, ...options }),
    error: (message: string, options?: Record<string, unknown>) => toastMocks.mockNotify({ type: 'error', message, ...options }),
    warning: (message: string, options?: Record<string, unknown>) => toastMocks.mockNotify({ type: 'warning', message, ...options }),
    info: (message: string, options?: Record<string, unknown>) => toastMocks.mockNotify({ type: 'info', message, ...options }),
    dismiss: toastMocks.dismiss,
    update: toastMocks.update,
    promise: toastMocks.promise,
  },
}))
const mockOnPlanInfoChanged = vi.fn()
const mockDeleteAppMutation = vi.fn().mockResolvedValue(undefined)
let mockDeleteMutationPending = false

vi.mock('@/next/navigation', () => ({
  useRouter: () => ({
    push: mockRouterPush,
  }),
}))

vi.mock('@tanstack/react-query', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@tanstack/react-query')>()
  return {
    ...actual,
    useQuery: () => ({
      data: [],
    }),
  }
})

vi.mock('@/next/dynamic', () => ({
  default: (loader: () => Promise<{ default: React.ComponentType }>) => {
    let Component: React.ComponentType<Record<string, unknown>> | null = null
    loader().then((mod) => {
      Component = mod.default as React.ComponentType<Record<string, unknown>>
    }).catch(() => {})
    const Wrapper = (props: Record<string, unknown>) => {
      if (Component)
        return <Component {...props} />
      return null
    }
    Wrapper.displayName = 'DynamicWrapper'
    return Wrapper
  },
}))

vi.mock('@/context/app-context', () => ({
  useAppContext: () => ({
    isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,
  }),
}))

vi.mock('@/context/provider-context', () => ({
  useProviderContext: () => ({
    onPlanInfoChanged: mockOnPlanInfoChanged,
  }),
}))

vi.mock('@/service/tag', () => ({
  fetchTagList: vi.fn().mockResolvedValue([]),
}))

vi.mock('@/service/use-apps', () => ({
  useDeleteAppMutation: () => ({
    mutateAsync: mockDeleteAppMutation,
    isPending: mockDeleteMutationPending,
  }),
}))

vi.mock('@/service/apps', () => ({
  deleteApp: vi.fn().mockResolvedValue({}),
  updateAppInfo: vi.fn().mockResolvedValue({}),
  copyApp: vi.fn().mockResolvedValue({ id: 'new-app-id', mode: 'chat' }),
  exportAppConfig: vi.fn().mockResolvedValue({ data: 'yaml-content' }),
}))

vi.mock('@/service/explore', () => ({
  fetchInstalledAppList: vi.fn().mockResolvedValue({ installed_apps: [] }),
}))

vi.mock('@/service/workflow', () => ({
  fetchWorkflowDraft: vi.fn().mockResolvedValue({ environment_variables: [] }),
}))

vi.mock('@/service/access-control', () => ({
  useGetUserCanAccessApp: () => ({ data: { result: true }, isLoading: false }),
}))

vi.mock('@/hooks/use-async-window-open', () => ({
  useAsyncWindowOpen: () => vi.fn(),
}))

// Mock modals loaded via next/dynamic
vi.mock('@/app/components/explore/create-app-modal', () => ({
  default: ({ show, onConfirm, onHide, appName }: Record<string, unknown>) => {
    if (!show)
      return null
    return (
      <div data-testid="edit-app-modal">
        <span data-testid="modal-app-name">{appName as string}</span>
        <button
          data-testid="confirm-edit"
          onClick={() => (onConfirm as (data: Record<string, unknown>) => void)({
            name: 'Updated App Name',
            icon_type: 'emoji',
            icon: '🔥',
            icon_background: '#fff',
            description: 'Updated description',
          })}
        >
          Confirm
        </button>
        <button data-testid="cancel-edit" onClick={onHide as () => void}>Cancel</button>
      </div>
    )
  },
}))

vi.mock('@/app/components/app/duplicate-modal', () => ({
  default: ({ show, onConfirm, onHide }: Record<string, unknown>) => {
    if (!show)
      return null
    return (
      <div data-testid="duplicate-app-modal">
        <button
          data-testid="confirm-duplicate"
          onClick={() => (onConfirm as (data: Record<string, unknown>) => void)({
            name: 'Copied App',
            icon_type: 'emoji',
            icon: '📋',
            icon_background: '#fff',
          })}
        >
          Confirm Duplicate
        </button>
        <button data-testid="cancel-duplicate" onClick={onHide as () => void}>Cancel</button>
      </div>
    )
  },
}))

vi.mock('@/app/components/app/switch-app-modal', () => ({
  default: ({ show, onClose, onSuccess }: Record<string, unknown>) => {
    if (!show)
      return null
    return (
      <div data-testid="switch-app-modal">
        <button data-testid="confirm-switch" onClick={onSuccess as () => void}>Confirm Switch</button>
        <button data-testid="cancel-switch" onClick={onClose as () => void}>Cancel</button>
      </div>
    )
  },
}))

vi.mock('@/app/components/workflow/dsl-export-confirm-modal', () => ({
  default: ({ onConfirm, onClose }: Record<string, unknown>) => (
    <div data-testid="dsl-export-confirm-modal">
      <button data-testid="export-include" onClick={() => (onConfirm as (include: boolean) => void)(true)}>Include</button>
      <button data-testid="export-close" onClick={onClose as () => void}>Close</button>
    </div>
  ),
}))

vi.mock('@/app/components/app/app-access-control', () => ({
  default: ({ onConfirm, onClose }: Record<string, unknown>) => (
    <div data-testid="access-control-modal">
      <button data-testid="confirm-access" onClick={onConfirm as () => void}>Confirm</button>
      <button data-testid="cancel-access" onClick={onClose as () => void}>Cancel</button>
    </div>
  ),
}))

const createMockApp = (overrides: Partial<App> = {}): App => ({
  id: overrides.id ?? 'app-1',
  name: overrides.name ?? 'Test Chat App',
  description: overrides.description ?? 'A chat application',
  author_name: overrides.author_name ?? 'Test Author',
  icon_type: overrides.icon_type ?? 'emoji',
  icon: overrides.icon ?? '🤖',
  icon_background: overrides.icon_background ?? '#FFEAD5',
  icon_url: overrides.icon_url ?? null,
  use_icon_as_answer_icon: overrides.use_icon_as_answer_icon ?? false,
  mode: overrides.mode ?? AppModeEnum.CHAT,
  enable_site: overrides.enable_site ?? true,
  enable_api: overrides.enable_api ?? true,
  api_rpm: overrides.api_rpm ?? 60,
  api_rph: overrides.api_rph ?? 3600,
  is_demo: overrides.is_demo ?? false,
  model_config: overrides.model_config ?? {} as App['model_config'],
  app_model_config: overrides.app_model_config ?? {} as App['app_model_config'],
  created_at: overrides.created_at ?? 1700000000,
  updated_at: overrides.updated_at ?? 1700001000,
  site: overrides.site ?? {} as App['site'],
  api_base_url: overrides.api_base_url ?? 'https://api.example.com',
  tags: overrides.tags ?? [],
  access_mode: overrides.access_mode ?? AccessMode.PUBLIC,
  max_active_requests: overrides.max_active_requests ?? null,
})

const mockOnRefresh = vi.fn()

const renderAppCard = (app?: Partial<App>) => {
  return renderWithSystemFeatures(
    <AppCard app={createMockApp(app)} onRefresh={mockOnRefresh} />,
    { systemFeatures: mockSystemFeatures },
  )
}

const openOperationsMenu = () => {
  fireEvent.click(screen.getByRole('button', { name: 'common.operation.more' }))
}

describe('App Card Operations Flow', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockDeleteMutationPending = false
    mockIsCurrentWorkspaceEditor = true
    mockSystemFeatures = {
      branding: { enabled: false },
      webapp_auth: { enabled: false },
    }
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  describe('Card Rendering', () => {
    it('should render app name and description', () => {
      renderAppCard({ name: 'My AI Bot', description: 'An intelligent assistant' })

      expect(screen.getByText('My AI Bot')).toBeInTheDocument()
      expect(screen.getByText('An intelligent assistant')).toBeInTheDocument()
    })

    it('should render author name', () => {
      renderAppCard({ author_name: 'John Doe' })

      expect(screen.getByText('John Doe')).toBeInTheDocument()
    })

    it('should navigate to app config page when card is clicked', () => {
      renderAppCard({ id: 'app-123', mode: AppModeEnum.CHAT })

      const card = screen.getByText('Test Chat App').closest('[class*="cursor-pointer"]')
      if (card)
        fireEvent.click(card)

      expect(mockRouterPush).toHaveBeenCalledWith('/app/app-123/configuration')
    })

    it('should navigate to workflow page for workflow apps', () => {
      renderAppCard({ id: 'app-wf', mode: AppModeEnum.WORKFLOW, name: 'WF App' })

      const card = screen.getByText('WF App').closest('[class*="cursor-pointer"]')
      if (card)
        fireEvent.click(card)

      expect(mockRouterPush).toHaveBeenCalledWith('/app/app-wf/workflow')
    })
  })

  // -- Delete flow --
  describe('Delete App Flow', () => {
    it('should show delete confirmation and call API on confirm', async () => {
      renderAppCard({ id: 'app-to-delete', name: 'Deletable App' })

      openOperationsMenu()
      fireEvent.click(await screen.findByText('common.operation.delete'))

      await waitFor(() => {
        expect(screen.getByText('app.deleteAppConfirmTitle')).toBeInTheDocument()
      })

      fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Deletable App' } })
      fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))

      await waitFor(() => {
        expect(mockDeleteAppMutation).toHaveBeenCalledWith('app-to-delete')
      })
    })
  })

  // -- Edit flow --
  describe('Edit App Flow', () => {
    it('should open edit modal and call updateAppInfo on confirm', async () => {
      renderAppCard({ id: 'app-edit', name: 'Editable App' })

      openOperationsMenu()
      fireEvent.click(await screen.findByText('app.editApp'))
      fireEvent.click(await screen.findByTestId('confirm-edit'))

      await waitFor(() => {
        expect(updateAppInfo).toHaveBeenCalledWith(
          expect.objectContaining({
            appID: 'app-edit',
            name: 'Updated App Name',
          }),
        )
      })
    })
  })

  // -- Export flow --
  describe('Export App Flow', () => {
    it('should call exportAppConfig for completion apps', async () => {
      renderAppCard({ id: 'app-export', mode: AppModeEnum.COMPLETION, name: 'Export App' })

      openOperationsMenu()
      fireEvent.click(await screen.findByText('app.export'))

      await waitFor(() => {
        expect(exportAppConfig).toHaveBeenCalledWith(
          expect.objectContaining({ appID: 'app-export' }),
        )
      })
    })
  })

  // -- Access mode display --
  describe('Access Mode Display', () => {
    it('should not render operations menu for non-editor users', () => {
      mockIsCurrentWorkspaceEditor = false
      renderAppCard({ name: 'Readonly App' })

      expect(screen.queryByText('app.editApp')).not.toBeInTheDocument()
      expect(screen.queryByText('common.operation.delete')).not.toBeInTheDocument()
    })
  })

  // -- Switch mode (only for CHAT/COMPLETION) --
  describe('Switch App Mode', () => {
    it('should show switch option for chat mode apps', async () => {
      renderAppCard({ id: 'app-switch', mode: AppModeEnum.CHAT })

      openOperationsMenu()

      await waitFor(() => {
        expect(screen.queryByText('app.switch')).toBeInTheDocument()
      })
    })

    it('should not show switch option for workflow apps', async () => {
      renderAppCard({ id: 'app-wf', mode: AppModeEnum.WORKFLOW, name: 'WF App' })

      openOperationsMenu()

      await waitFor(() => {
        expect(screen.queryByText('app.switch')).not.toBeInTheDocument()
      })
    })
  })
})
