import React from 'react';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { BulkChangeOwnerModal } from '../src/platform/features/record/components/BulkChangeOwnerModal';
import { MetadataRecordList } from '../src/platform/features/record/components/MetadataRecordList';
import type { SObjectDescribe, SObjectField } from '../src/platform/types';

const testState = vi.hoisted(() => ({
  canEditObject: true,
  canDeleteObject: false,
  records: [] as Array<Record<string, unknown>>,
  refreshRecords: vi.fn(),
  showError: vi.fn(),
  showSuccess: vi.fn(),
}));

const actionAPIMock = vi.hoisted(() => ({
  getStandardActions: vi.fn(),
}));

const dataAPIMock = vi.hoisted(() => ({
  changeOwner: vi.fn(),
  query: vi.fn(),
  updateRecord: vi.fn(),
  deleteRecord: vi.fn(),
}));

vi.mock('@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui', () => ({
  Button: ({
    children,
    disabled,
    icon,
    loading,
    onClick,
    title,
    type,
  }: React.ButtonHTMLAttributes<HTMLButtonElement> & {
    icon?: React.ReactNode;
    loading?: boolean;
  }) => (
    <button
      type={type ?? 'button'}
      disabled={disabled || loading}
      onClick={onClick}
      title={title}
    >
      {icon}
      {loading ? 'Loading...' : children}
    </button>
  ),
  BaseModal: ({
    children,
    footer,
    isOpen,
    title,
  }: {
    children: React.ReactNode;
    footer?: React.ReactNode;
    isOpen: boolean;
    title: React.ReactNode;
  }) => isOpen ? (
    <section role="dialog" aria-label={String(title)}>
      <h1>{title}</h1>
      {children}
      {footer}
    </section>
  ) : null,
  ConfirmationModal: ({ isOpen, title }: { isOpen: boolean; title: string }) => (
    isOpen ? <section role="dialog" aria-label={title} /> : null
  ),
  OverflowMenu: ({
    items,
    'aria-label': ariaLabel = 'More actions',
  }: {
    items: Array<{ key: string; label: React.ReactNode; onSelect: () => void; disabled?: boolean }>;
    'aria-label'?: string;
  }) => (
    <div data-testid="overflow-menu">
      <button type="button" aria-haspopup="menu" aria-label={ariaLabel}>More</button>
      <div role="menu" aria-label={ariaLabel}>
        {items.map((item) => (
          <button key={item.key} type="button" role="menuitem" disabled={item.disabled} onClick={item.onSelect}>
            {item.label}
          </button>
        ))}
      </div>
    </div>
  ),
  Pagination: () => <nav aria-label="Pagination" />,
  RecordListSkeleton: ({ message }: { message?: string }) => <div>{message}</div>,
  DEBOUNCE_FAST_MS: 0,
  useErrorToast: () => testState.showError,
  useSuccessToast: () => testState.showSuccess,
}));

vi.mock('../src/platform/infrastructure/api/actions', () => ({
  actionAPI: actionAPIMock,
}));

vi.mock('../src/platform/infrastructure/api/data', () => ({
  dataAPI: dataAPIMock,
}));

vi.mock('../src/platform/infrastructure/api/schemas', () => ({
  getInternalObjectMetadata: vi.fn(),
  getListViewDescribe: vi.fn(),
  extractWhereClause: vi.fn(),
}));

vi.mock('../src/platform/infrastructure/api/files', () => ({
  filesAPI: {
    deleteRecordFile: vi.fn(),
  },
}));

vi.mock('../src/platform/features/record/hooks/useRecordList', () => ({
  useRecordList: () => ({
    records: testState.records,
    hasResolvedQuery: true,
    refresh: testState.refreshRecords,
    totalSize: testState.records.length,
    totalPages: 1,
    hasKnownTotal: true,
    hasNext: false,
  }),
}));

vi.mock('../src/platform/features/record/hooks/useListViews', () => ({
  useListViews: () => ({
    listViews: [],
    loading: false,
    error: null,
    refresh: vi.fn(),
    createListView: vi.fn(),
    updateListView: vi.fn(),
    deleteListView: vi.fn(),
  }),
}));

vi.mock('../src/platform/features/record/hooks/useReportFolderFilter', () => ({
  useReportFolderFilter: (_objectApiName: string, combinedFilterExpr: string) => ({
    folderFilter: null,
    clearFolderFilter: vi.fn(),
    actionableFields: [],
    handleCellAction: vi.fn(),
    effectiveFilterExpr: combinedFilterExpr,
    extraSelectFields: [],
  }),
}));

vi.mock('../src/platform/components/SplitViewContainer', () => ({
  useSplitView: () => ({
    selectedRecordId: null,
    isSplitMode: false,
    openInSplit: vi.fn(),
    closeSplit: vi.fn(),
  }),
}));

vi.mock('../src/platform/permissions/objectPermissions', () => ({
  useCanCreateObject: () => true,
  useCanDeleteObject: () => testState.canDeleteObject,
  useCanEditObject: () => testState.canEditObject,
}));

vi.mock('../src/platform/features/record/components/RecordListTable', () => ({
  RecordListTable: ({ toggleSelectAll, toggleSelectRecord }: {
    toggleSelectAll: () => void;
    toggleSelectRecord: (recordId: string) => void;
  }) => (
    <div data-testid="record-list-table">
      <button type="button" onClick={() => toggleSelectRecord('001ACCOUNT')}>
        Select Account
      </button>
      <button type="button" onClick={toggleSelectAll}>
        Select All Rows
      </button>
    </div>
  ),
}));

vi.mock('../src/platform/components/kanban/KanbanBoard', () => ({
  KanbanBoard: () => <div data-testid="kanban-board" />,
}));

vi.mock('../src/platform/features/record/components/SplitViewPanel', () => ({
  SplitViewPanel: () => <div data-testid="split-view-panel" />,
}));

vi.mock('../src/platform/features/record/components/BulkEditModal', () => ({
  BulkEditModal: () => null,
}));

vi.mock('../src/platform/features/admin/components/CreateObjectWizard', () => ({
  CreateObjectWizard: () => null,
}));

vi.mock('../src/platform/features/record/components/SelectFieldsModal', () => ({
  SelectFieldsModal: () => null,
}));

const fields: SObjectField[] = [
  {
    name: 'Name',
    label: 'Account Name',
    type: 'string',
    custom: false,
    readable: true,
    createable: true,
    updateable: true,
    nillable: true,
  },
  {
    name: 'OwnerId',
    label: 'Owner',
    type: 'reference',
    custom: false,
    readable: true,
    createable: false,
    updateable: false,
    nillable: false,
    referenceTo: ['User'],
  },
];

const accountMetadata: SObjectDescribe = {
  name: 'Account',
  label: 'Account',
  labelPlural: 'Accounts',
  fields,
} as SObjectDescribe;

function changeOwnerAction(targetObjects: string[] = ['Account']) {
  return {
    name: 'ChangeOwner',
    label: 'Change Owner',
    type: 'standard',
    rendererId: 'app-pack:crm/action-renderer/change-owner',
    targetObjects,
  };
}

function renderRecordList() {
  return render(<MetadataRecordList objectMetadata={accountMetadata} />);
}

async function searchAndSelectUser() {
  fireEvent.change(screen.getByPlaceholderText('Search users...'), {
    target: { value: 'Ada' },
  });
  fireEvent.click(screen.getByRole('button', { name: /search/i }));
  await screen.findByText('Ada Lovelace');
  fireEvent.click(screen.getByText('Ada Lovelace'));
}

beforeEach(() => {
  testState.canEditObject = true;
  testState.canDeleteObject = false;
  testState.records = [{ Id: '001ACCOUNT', Name: 'Acme' }];
  testState.refreshRecords.mockClear();
  testState.showError.mockClear();
  testState.showSuccess.mockClear();
  actionAPIMock.getStandardActions.mockResolvedValue({ actions: [changeOwnerAction()] });
  dataAPIMock.query.mockResolvedValue([
    {
      Id: '005USER',
      FirstName: 'Ada',
      LastName: 'Lovelace',
      Title: 'Sales Ops',
      Email: 'ada@example.invalid',
    },
  ]);
  dataAPIMock.changeOwner.mockResolvedValue({
    recordId: '001ACCOUNT',
    previousOwnerId: '005OLD',
    newOwnerId: '005USER',
  });
});

afterEach(() => {
  vi.clearAllMocks();
});

describe('MetadataRecordList bulk Change Owner toolbar', () => {
  it('shows Change Owner from standard action availability even when generic edit is false', async () => {
    testState.canEditObject = false;

    renderRecordList();
    fireEvent.click(screen.getByRole('button', { name: /select account/i }));

    expect(await screen.findByRole('button', { name: /change owner/i })).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /edit selected/i })).not.toBeInTheDocument();
  });

  it('hides Change Owner when the standard action is unavailable even if generic edit is true', async () => {
    actionAPIMock.getStandardActions.mockResolvedValue({ actions: [] });
    testState.canEditObject = true;

    renderRecordList();
    fireEvent.click(screen.getByRole('button', { name: /select account/i }));

    expect(await screen.findByRole('button', { name: /edit selected/i })).toBeInTheDocument();
    await waitFor(() => {
      expect(screen.queryByRole('button', { name: /change owner/i })).not.toBeInTheDocument();
    });
  });

  it('opens the bulk Change Owner modal for selected records', async () => {
    renderRecordList();
    fireEvent.click(screen.getByRole('button', { name: /select account/i }));
    fireEvent.click(await screen.findByRole('button', { name: /change owner/i }));

    const dialog = screen.getByRole('dialog', { name: 'Change Owner' });
    expect(dialog).toBeInTheDocument();
    expect(within(dialog).getByText('1 record selected')).toBeInTheDocument();
  });

  it('blocks selections over 50 before opening the modal and sends no changeOwner request', async () => {
    testState.records = Array.from({ length: 51 }, (_, index) => ({
      Id: `001ACCOUNT${String(index).padStart(2, '0')}`,
      Name: `Account ${index}`,
    }));

    renderRecordList();
    fireEvent.click(screen.getByRole('button', { name: /select all rows/i }));
    fireEvent.click(await screen.findByRole('button', { name: /change owner/i }));

    expect(screen.queryByRole('dialog', { name: 'Change Owner' })).not.toBeInTheDocument();
    expect(dataAPIMock.changeOwner).not.toHaveBeenCalled();
    expect(testState.showError).toHaveBeenCalledWith('Select 50 or fewer records to change owner.');
  });
});

describe('BulkChangeOwnerModal', () => {
  it('changes every selected record owner and reports all-success counts', async () => {
    const onSuccess = vi.fn();
    const onClose = vi.fn();

    render(
      <BulkChangeOwnerModal
        isOpen
        onClose={onClose}
        objectApiName="Account"
        recordIds={['001ACCOUNT', '001ACCOUNT2']}
        onSuccess={onSuccess}
      />,
    );

    await searchAndSelectUser();
    fireEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => {
      expect(dataAPIMock.changeOwner).toHaveBeenCalledTimes(2);
    });
    expect(dataAPIMock.changeOwner).toHaveBeenNthCalledWith(1, {
      recordId: '001ACCOUNT',
      objectApiName: 'Account',
      newOwnerId: '005USER',
      sendNotification: true,
    });
    expect(dataAPIMock.changeOwner).toHaveBeenNthCalledWith(2, {
      recordId: '001ACCOUNT2',
      objectApiName: 'Account',
      newOwnerId: '005USER',
      sendNotification: true,
    });
    expect(testState.showSuccess).toHaveBeenCalledWith('2 succeeded / 0 failed');
    expect(onSuccess).toHaveBeenCalledTimes(1);
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it('continues after a per-record failure and reports partial failure counts', async () => {
    const onSuccess = vi.fn();
    dataAPIMock.changeOwner
      .mockResolvedValueOnce({ recordId: '001ACCOUNT', previousOwnerId: '005OLD', newOwnerId: '005USER' })
      .mockRejectedValueOnce(new Error('record locked'));

    render(
      <BulkChangeOwnerModal
        isOpen
        onClose={vi.fn()}
        objectApiName="Account"
        recordIds={['001ACCOUNT', '001ACCOUNT2']}
        onSuccess={onSuccess}
      />,
    );

    await searchAndSelectUser();
    fireEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => {
      expect(dataAPIMock.changeOwner).toHaveBeenCalledTimes(2);
    });
    expect(testState.showError).toHaveBeenCalledWith('1 succeeded / 1 failed');
    expect(onSuccess).toHaveBeenCalledTimes(1);
  });
});
