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

import { type InertiaFormProps, useForm } from '@inertiajs/react';

import StatusChangeModal from './StatusChangeModal';

// Mock useForm from Inertia
const mockPatch = vi.fn();
const mockSetData = vi.fn();

// Create a mocked version of useForm
const mockedUseForm = vi.mocked(useForm);

type StatusFormData = { lifecycle_status: string; status_notes: string };

function mockFormReturn(
  overrides: Partial<InertiaFormProps<StatusFormData>> = {},
): InertiaFormProps<StatusFormData> {
  return {
    data: { lifecycle_status: 'pending', status_notes: '' },
    isDirty: false,
    errors: {},
    hasErrors: false,
    processing: false,
    progress: null,
    wasSuccessful: false,
    recentlySuccessful: false,
    setData: mockSetData,
    transform: vi.fn(),
    setDefaults: vi.fn() as InertiaFormProps<StatusFormData>['setDefaults'],
    reset: vi.fn(),
    clearErrors: vi.fn(),
    resetAndClearErrors: vi.fn(),
    setError: vi.fn() as InertiaFormProps<StatusFormData>['setError'],
    submit: vi.fn(),
    get: vi.fn(),
    patch: mockPatch,
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
    cancel: vi.fn(),
    dontRemember: vi.fn() as InertiaFormProps<StatusFormData>['dontRemember'],
    withPrecognition: vi.fn() as unknown as InertiaFormProps<StatusFormData>['withPrecognition'],
    ...overrides,
  } as unknown as InertiaFormProps<StatusFormData>;
}

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    useForm: vi.fn(() => ({
      data: { lifecycle_status: 'pending', status_notes: '' },
      setData: mockSetData,
      patch: mockPatch,
      processing: false,
      errors: {},
    })),
  };
});

// Mock route helper
vi.mock('ziggy-js', () => ({
  default: vi.fn((name: string, params: unknown[]) => {
    return `/sites/${params[0]}/recommendations/${params[1]}/status`;
  }),
}));

global.route = vi.fn((name: string, params?: unknown) => {
  if (name === 'recommendations.update-status' && Array.isArray(params)) {
    return `/sites/${params[0]}/recommendations/${params[1]}/status`;
  }
  return '/';
}) as typeof route;

describe('StatusChangeModal', () => {
  const defaultProps = {
    open: true,
    onOpenChange: vi.fn(),
    siteId: 1,
    recommendationId: 42,
    currentStatus: 'pending',
  };

  beforeEach(() => {
    vi.clearAllMocks();
    // Reset the mock implementation
    mockedUseForm.mockReturnValue(mockFormReturn());
  });

  // ============================================
  // Rendering tests
  // ============================================

  describe('rendering', () => {
    it('renders the modal when open is true', () => {
      render(<StatusChangeModal {...defaultProps} />);

      expect(screen.getByText('Change Recommendation Status')).toBeInTheDocument();
      expect(screen.getByText(/update the lifecycle status/i)).toBeInTheDocument();
    });

    it('renders status select with all lifecycle status options', async () => {
      const user = userEvent.setup();
      render(<StatusChangeModal {...defaultProps} />);

      // Radix Select trigger renders as a button
      const trigger = screen.getByRole('combobox');
      expect(trigger).toBeInTheDocument();

      // Open the dropdown to see options
      await user.click(trigger);

      expect(screen.getByRole('option', { name: 'Pending' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Reviewed' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Approved' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Rejected' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Deferred' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Applied' })).toBeInTheDocument();
      expect(screen.getByRole('option', { name: 'Tracking' })).toBeInTheDocument();
    });

    it('renders notes textarea', () => {
      render(<StatusChangeModal {...defaultProps} />);

      const textarea = screen.getByLabelText('Notes (optional)');
      expect(textarea).toBeInTheDocument();
      expect(textarea).toHaveAttribute('placeholder', 'Add any notes about this status change...');
    });

    it('renders Cancel and Update Status buttons', () => {
      render(<StatusChangeModal {...defaultProps} />);

      expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
      expect(screen.getByRole('button', { name: 'Update Status' })).toBeInTheDocument();
    });

    it('selects current status by default', () => {
      mockedUseForm.mockReturnValue(
        mockFormReturn({
          data: { lifecycle_status: 'approved', status_notes: '' },
        }),
      );

      render(<StatusChangeModal {...defaultProps} currentStatus="approved" />);

      // Radix Select shows the current value in the trigger
      const trigger = screen.getByRole('combobox');
      expect(trigger).toHaveTextContent('Approved');
    });
  });

  // ============================================
  // Interaction tests
  // ============================================

  describe('interactions', () => {
    it('calls setData when status is changed', async () => {
      const user = userEvent.setup();
      render(<StatusChangeModal {...defaultProps} />);

      // Open the Radix Select and click an option
      const trigger = screen.getByRole('combobox');
      await user.click(trigger);
      await user.click(screen.getByRole('option', { name: 'Approved' }));

      expect(mockSetData).toHaveBeenCalledWith('lifecycle_status', 'approved');
    });

    it('calls setData when notes are typed', async () => {
      const user = userEvent.setup();
      render(<StatusChangeModal {...defaultProps} />);

      const textarea = screen.getByLabelText('Notes (optional)');
      await user.type(textarea, 'Test note');

      // Should be called once per character typed
      expect(mockSetData).toHaveBeenCalled();
      expect(mockSetData).toHaveBeenCalledWith('status_notes', expect.any(String));
    });

    it('calls onOpenChange when Cancel button is clicked', async () => {
      const user = userEvent.setup();
      const onOpenChange = vi.fn();
      render(<StatusChangeModal {...defaultProps} onOpenChange={onOpenChange} />);

      const cancelButton = screen.getByRole('button', { name: 'Cancel' });
      await user.click(cancelButton);

      expect(onOpenChange).toHaveBeenCalledWith(false);
    });

    it('submits form with patch request when Update Status is clicked', async () => {
      const user = userEvent.setup();
      render(<StatusChangeModal {...defaultProps} />);

      const submitButton = screen.getByRole('button', { name: 'Update Status' });
      await user.click(submitButton);

      expect(mockPatch).toHaveBeenCalledWith(
        '/sites/1/recommendations/42/status',
        expect.objectContaining({
          onSuccess: expect.any(Function),
        }),
      );
    });

    it('calls onOpenChange(false) after successful form submission', async () => {
      const user = userEvent.setup();
      const onOpenChange = vi.fn();

      // Mock successful submission
      mockPatch.mockImplementation((url, options) => {
        if (options?.onSuccess) {
          options.onSuccess();
        }
      });

      render(<StatusChangeModal {...defaultProps} onOpenChange={onOpenChange} />);

      const submitButton = screen.getByRole('button', { name: 'Update Status' });
      await user.click(submitButton);

      await waitFor(() => {
        expect(onOpenChange).toHaveBeenCalledWith(false);
      });
    });
  });

  // ============================================
  // Error handling tests
  // ============================================

  describe('error handling', () => {
    it('displays validation error for lifecycle_status', () => {
      mockedUseForm.mockReturnValue(
        mockFormReturn({
          errors: {
            lifecycle_status: 'The lifecycle status field is required.',
          } as InertiaFormProps<StatusFormData>['errors'],
        }),
      );

      render(<StatusChangeModal {...defaultProps} />);

      expect(screen.getByText('The lifecycle status field is required.')).toBeInTheDocument();
    });

    it('displays validation error for status_notes', () => {
      mockedUseForm.mockReturnValue(
        mockFormReturn({
          errors: {
            status_notes: 'The status notes field must not exceed 1000 characters.',
          } as InertiaFormProps<StatusFormData>['errors'],
        }),
      );

      render(<StatusChangeModal {...defaultProps} />);

      expect(
        screen.getByText('The status notes field must not exceed 1000 characters.'),
      ).toBeInTheDocument();
    });
  });

  // ============================================
  // Processing state tests
  // ============================================

  describe('processing state', () => {
    it('shows loading state when form is processing', () => {
      mockedUseForm.mockReturnValue(mockFormReturn({ processing: true }));

      render(<StatusChangeModal {...defaultProps} />);

      // When processing, LoadingButton changes aria-label to "Update Status in progress"
      const submitButton = screen.getByRole('button', { name: /Update Status|in progress/i });
      // LoadingButton should disable the button when processing
      expect(submitButton).toBeDisabled();
    });
  });

  // ============================================
  // Modal behavior tests
  // ============================================

  describe('modal behavior', () => {
    it('does not render modal content when open is false', () => {
      render(<StatusChangeModal {...defaultProps} open={false} />);

      expect(screen.queryByText('Change Recommendation Status')).not.toBeInTheDocument();
    });
  });
});
