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

import { useForm } from '@inertiajs/react';

import CreateTaskDialog from './CreateTaskDialog';

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

// Create a mocked version of useForm (typed loosely to allow partial mock return values)
const mockedUseForm = vi.mocked(useForm) as unknown as { mockReturnValue: (val: unknown) => void };

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    useForm: vi.fn(() => ({
      data: {
        title: '',
        description: '',
        due_date: '',
        scheduled_date: '',
        status: 'planned' as const,
        page_url: '',
      },
      setData: mockSetData,
      post: mockPost,
      processing: false,
      errors: {},
      reset: mockReset,
    })),
  };
});

describe('CreateTaskDialog', () => {
  const user = userEvent.setup();
  const mockOnClose = vi.fn();

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

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

  describe('rendering', () => {
    it('renders when open is true', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByText('Create New Task')).toBeInTheDocument();
      expect(screen.getByText(/Add a new task to your SEO calendar/i)).toBeInTheDocument();
    });

    it('does not render when open is false', () => {
      render(<CreateTaskDialog open={false} onClose={mockOnClose} siteId={1} />);

      expect(screen.queryByText('Create New Task')).not.toBeInTheDocument();
    });

    it('renders all required form fields', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/description/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/due date/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/scheduled date/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/page url/i)).toBeInTheDocument();
    });

    it('marks required fields with asterisk', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleLabel = screen.getByText('Title');
      const dueDateLabel = screen.getByText('Due Date');
      const statusLabel = screen.getByText('Status');

      expect(titleLabel.parentElement?.querySelector('.text-destructive')).toBeInTheDocument();
      expect(dueDateLabel.parentElement?.querySelector('.text-destructive')).toBeInTheDocument();
      expect(statusLabel.parentElement?.querySelector('.text-destructive')).toBeInTheDocument();
    });

    it('renders action buttons', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
      expect(screen.getByRole('button', { name: /create task/i })).toBeInTheDocument();
    });
  });

  // ============================================
  // Initial values tests
  // ============================================

  describe('initial values', () => {
    it('pre-fills due date when initialDueDate is provided', () => {
      render(
        <CreateTaskDialog
          open={true}
          onClose={mockOnClose}
          siteId={1}
          initialDueDate="2026-03-15"
        />,
      );

      expect(mockSetData).toHaveBeenCalledWith('due_date', '2026-03-15');
    });

    it('does not pre-fill due date when initialDueDate is not provided', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const dueDateInput = screen.getByLabelText(/due date/i) as HTMLInputElement;
      expect(dueDateInput.value).toBe('');
    });

    it('defaults status to planned', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const statusSelect = screen.getByRole('combobox');
      expect(statusSelect).toHaveTextContent('Planned');
    });
  });

  // ============================================
  // Form interaction tests
  // ============================================

  describe('form interactions', () => {
    it('calls setData when title changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i);
      await user.type(titleInput, 'Test task');

      expect(mockSetData).toHaveBeenCalledWith('title', expect.any(String));
    });

    it('calls setData when description changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const descriptionInput = screen.getByLabelText(/description/i);
      await user.type(descriptionInput, 'Test description');

      expect(mockSetData).toHaveBeenCalledWith('description', expect.any(String));
    });

    it('calls setData when due date changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const dueDateInput = screen.getByLabelText(/due date/i);
      fireEvent.change(dueDateInput, { target: { value: '2026-03-20' } });

      expect(mockSetData).toHaveBeenCalledWith('due_date', '2026-03-20');
    });

    it('calls setData when scheduled date changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const scheduledDateInput = screen.getByLabelText(/scheduled date/i);
      fireEvent.change(scheduledDateInput, { target: { value: '2026-03-20T10:00' } });

      expect(mockSetData).toHaveBeenCalledWith('scheduled_date', '2026-03-20T10:00');
    });

    it('calls setData when page URL changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const pageUrlInput = screen.getByLabelText(/page url/i);
      await user.type(pageUrlInput, 'https://example.com');

      expect(mockSetData).toHaveBeenCalledWith('page_url', expect.any(String));
    });

    it('updates status when selection changes', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const statusSelect = screen.getByRole('combobox');
      await user.click(statusSelect);

      const inProgressOption = screen.getByRole('option', { name: /in progress/i });
      await user.click(inProgressOption);

      expect(mockSetData).toHaveBeenCalledWith('status', 'in_progress');
    });
  });

  // ============================================
  // Client-side validation tests
  // ============================================

  describe('client-side validation', () => {
    it('shows error when title is empty on blur', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i);
      await user.click(titleInput);
      await user.tab();

      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument();
      });
    });

    it('shows error when title exceeds 500 characters', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i) as HTMLInputElement;
      const longTitle = 'a'.repeat(501);
      titleInput.value = longTitle;
      fireEvent.blur(titleInput);

      await waitFor(() => {
        expect(screen.getByText(/title must not exceed 500 characters/i)).toBeInTheDocument();
      });
    });

    it('shows error when due date is empty on blur', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const dueDateInput = screen.getByLabelText(/due date/i);
      await user.click(dueDateInput);
      await user.tab();

      await waitFor(() => {
        expect(screen.getByText(/due date is required/i)).toBeInTheDocument();
      });
    });

    it('shows error when page URL is invalid', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const pageUrlInput = screen.getByLabelText(/page url/i) as HTMLInputElement;
      pageUrlInput.value = 'not-a-url';
      fireEvent.blur(pageUrlInput);

      await waitFor(() => {
        expect(screen.getByText(/must be a valid url/i)).toBeInTheDocument();
      });
    });

    it('accepts valid URL for page URL', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const pageUrlInput = screen.getByLabelText(/page url/i) as HTMLInputElement;
      pageUrlInput.value = 'https://example.com/page';
      fireEvent.blur(pageUrlInput);

      await waitFor(() => {
        expect(screen.queryByText(/must be a valid url/i)).not.toBeInTheDocument();
      });
    });

    it('clears title error when user starts typing', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i);

      // Trigger error
      await user.click(titleInput);
      await user.tab();

      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument();
      });

      // Start typing to clear error
      await user.click(titleInput);
      await user.type(titleInput, 't');

      expect(mockSetData).toHaveBeenCalled();
    });

    it('prevents form submission with validation errors', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const submitButton = screen.getByRole('button', { name: /create task/i });
      await user.click(submitButton);

      // post should not be called due to validation failure
      expect(mockPost).not.toHaveBeenCalled();
    });
  });

  // ============================================
  // Form submission tests
  // ============================================

  describe('form submission', () => {
    it('submits form with valid data', async () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: 'Test Task',
          description: 'Test Description',
          due_date: '2026-03-20',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const form = screen.getByRole('button', { name: /create task/i }).closest('form');
      fireEvent.submit(form!);

      await waitFor(() => {
        expect(mockPost).toHaveBeenCalledWith(
          expect.stringContaining('/sites/1/calendar'),
          expect.objectContaining({
            onSuccess: expect.any(Function),
            preserveScroll: true,
          }),
        );
      });
    });

    it('calls onClose after successful submission', async () => {
      const mockPostWithSuccess = vi.fn((url, options) => {
        options?.onSuccess?.();
      });

      mockedUseForm.mockReturnValue({
        data: {
          title: 'Test Task',
          description: 'Test Description',
          due_date: '2026-03-20',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPostWithSuccess,
        processing: false,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const form = screen.getByRole('button', { name: /create task/i }).closest('form');
      fireEvent.submit(form!);

      await waitFor(() => {
        expect(mockOnClose).toHaveBeenCalled();
        expect(mockReset).toHaveBeenCalled();
      });
    });

    it('does not submit when title is missing', async () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: 'Test Description',
          due_date: '2026-03-20',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const form = screen.getByRole('button', { name: /create task/i }).closest('form');
      fireEvent.submit(form!);

      await waitFor(() => {
        expect(mockPost).not.toHaveBeenCalled();
      });
    });
  });

  // ============================================
  // Dialog behavior tests
  // ============================================

  describe('dialog behavior', () => {
    it('calls onClose when cancel button is clicked', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

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

      expect(mockOnClose).toHaveBeenCalled();
      expect(mockReset).toHaveBeenCalled();
    });

    it('resets form when dialog is opened', () => {
      const { rerender } = render(
        <CreateTaskDialog open={false} onClose={mockOnClose} siteId={1} />,
      );

      rerender(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(mockReset).toHaveBeenCalled();
    });

    it('resets form when dialog is closed', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

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

      expect(mockReset).toHaveBeenCalled();
    });
  });

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

  describe('processing state', () => {
    it('shows "Creating..." when processing', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: true,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByRole('button', { name: /creating/i })).toBeInTheDocument();
    });

    it('disables submit button when processing', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: true,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const submitButton = screen.getByRole('button', { name: /creating/i });
      expect(submitButton).toBeDisabled();
    });

    it('disables cancel button when processing', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: true,
        errors: {},
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const cancelButton = screen.getByRole('button', { name: /cancel/i });
      expect(cancelButton).toBeDisabled();
    });
  });

  // ============================================
  // Server-side error display tests
  // ============================================

  describe('server-side errors', () => {
    it('displays server title error', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: { title: 'The title field is required.' },
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByText(/the title field is required/i)).toBeInTheDocument();
    });

    it('displays server due_date error', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: { due_date: 'The due date must be a valid date.' },
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByText(/the due date must be a valid date/i)).toBeInTheDocument();
    });

    it('displays server page_url error', () => {
      mockedUseForm.mockReturnValue({
        data: {
          title: '',
          description: '',
          due_date: '',
          scheduled_date: '',
          status: 'planned' as const,
          page_url: '',
        },
        setData: mockSetData,
        post: mockPost,
        processing: false,
        errors: { page_url: 'The page url must be a valid URL.' },
        reset: mockReset,
      } as unknown as ReturnType<typeof useForm>);

      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByText(/the page url must be a valid url/i)).toBeInTheDocument();
    });
  });

  // ============================================
  // Accessibility tests
  // ============================================

  describe('accessibility', () => {
    it('has proper labels for all form fields', () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/description/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/due date/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/scheduled date/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/page url/i)).toBeInTheDocument();
    });

    it('sets aria-invalid on fields with errors', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i);
      await user.click(titleInput);
      await user.tab();

      await waitFor(() => {
        expect(titleInput).toHaveAttribute('aria-invalid', 'true');
      });
    });

    it('sets aria-describedby on fields with errors', async () => {
      render(<CreateTaskDialog open={true} onClose={mockOnClose} siteId={1} />);

      const titleInput = screen.getByLabelText(/title/i);
      await user.click(titleInput);
      await user.tab();

      await waitFor(() => {
        expect(titleInput).toHaveAttribute('aria-describedby', 'title-error');
      });
    });
  });
});
