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

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

const { mockListStores, mockCreateStore, mockUpdateStore, mockDeactivateStore } = vi.hoisted(
  () => ({
    mockListStores: vi.fn(),
    mockCreateStore: vi.fn(),
    mockUpdateStore: vi.fn(),
    mockDeactivateStore: vi.fn(),
  }),
);

vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));

vi.mock('@/services/stores', () => ({
  listStores: mockListStores,
  createStore: mockCreateStore,
  updateStore: mockUpdateStore,
  deactivateStore: mockDeactivateStore,
}));

vi.mock('next/link', () => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return { default: (props: any) => props.children };
});

import StoresPage from '../../../../src/app/(dashboard)/stores/page';

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

describe('StoresPage', () => {
  it('shows loading skeleton when session is loading', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: true });

    render(<StoresPage />);

    expect(screen.getByText('Stores')).toBeInTheDocument();
    expect(screen.queryByText('Add Store')).not.toBeInTheDocument();
  });

  it('shows household prompt when no householdId', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false });

    render(<StoresPage />);

    expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
  });

  it('renders store list when household exists', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: ['pharmacy'],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => {
      expect(screen.getByText('Walgreens')).toBeInTheDocument();
    });
  });

  it('shows empty state when no stores', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => {
      expect(screen.getByText(/No stores yet/)).toBeInTheDocument();
    });
  });

  it('shows Add Store form when button clicked', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => {
      expect(screen.getByText('Add Store')).toBeInTheDocument();
    });

    await userEvent.click(screen.getByText('Add Store'));

    expect(screen.getByPlaceholderText('e.g. Walgreens')).toBeInTheDocument();
  });

  it('shows error when store list fails to load', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockRejectedValue(new Error('Network error'));

    render(<StoresPage />);

    await waitFor(() => {
      expect(screen.getByText('Network error')).toBeInTheDocument();
    });
  });

  it('deactivates store after confirmation', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          updatedAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeactivateStore.mockResolvedValue({});
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<StoresPage />);

    await waitFor(() => {
      expect(screen.getByText('CVS')).toBeInTheDocument();
    });

    await userEvent.click(screen.getByTitle('Deactivate'));

    expect(mockDeactivateStore).toHaveBeenCalledWith('hh1', 'st-1');
  });

  it('creates a store on form submit', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
    mockCreateStore.mockResolvedValue({
      _id: 'st-new',
      name: 'Walmart',
      tags: [],
      isActive: true,
      createdAt: '2026-01-01T00:00:00.000Z',
      householdId: 'hh1',
      createdBy: 'u-1',
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
    fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), {
      target: { value: 'Walmart' },
    });
    fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);

    await waitFor(() =>
      expect(mockCreateStore).toHaveBeenCalledWith(
        'hh1',
        expect.objectContaining({ name: 'Walmart' }),
      ),
    );
  });

  it('shows error when create store fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
    mockCreateStore.mockRejectedValue(new Error('Store already exists'));

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
    fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), {
      target: { value: 'Walmart' },
    });
    fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);

    await waitFor(() => expect(screen.getByText('Store already exists')).toBeInTheDocument());
  });

  it('opens edit form for a store', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('CVS'));
    await userEvent.click(screen.getByTitle('Edit'));

    expect(screen.getByDisplayValue('CVS')).toBeInTheDocument();
    expect(screen.getByText('Edit Store')).toBeInTheDocument();
  });

  it('saves edited store', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockUpdateStore.mockResolvedValue({});

    render(<StoresPage />);

    await waitFor(() => screen.getByText('CVS'));
    await userEvent.click(screen.getByTitle('Edit'));

    await waitFor(() => screen.getByDisplayValue('CVS'));
    fireEvent.change(screen.getByDisplayValue('CVS'), { target: { value: 'CVS Pharmacy' } });
    fireEvent.submit(screen.getByDisplayValue('CVS Pharmacy').closest('form')!);

    await waitFor(() =>
      expect(mockUpdateStore).toHaveBeenCalledWith(
        'hh1',
        'st-1',
        expect.objectContaining({ name: 'CVS Pharmacy' }),
      ),
    );
  });

  it('dismisses error', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockRejectedValue(new Error('Network error'));

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Network error'));
    await userEvent.click(screen.getByText('Dismiss'));

    expect(screen.queryByText('Network error')).not.toBeInTheDocument();
  });

  it('shows error when deactivate fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockDeactivateStore.mockRejectedValue(new Error('Deactivate failed'));
    vi.spyOn(window, 'confirm').mockReturnValue(true);

    render(<StoresPage />);

    await waitFor(() => screen.getByTitle('Deactivate'));
    await userEvent.click(screen.getByTitle('Deactivate'));

    await waitFor(() => expect(screen.getByText('Deactivate failed')).toBeInTheDocument());
  });

  it('cancels the create store form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
    await userEvent.click(
      screen.getAllByRole('button', { name: 'Cancel' })[
        screen.getAllByRole('button', { name: 'Cancel' }).length - 1
      ]!,
    );

    expect(screen.queryByPlaceholderText('e.g. Walgreens')).not.toBeInTheDocument();
  });

  it('cancels the edit store form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('CVS'));
    await userEvent.click(screen.getByTitle('Edit'));

    await waitFor(() => screen.getByDisplayValue('CVS'));
    await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));

    expect(screen.queryByDisplayValue('CVS')).not.toBeInTheDocument();
  });

  it('filters stores by tag', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: ['pharmacy'],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
        {
          _id: 'st-2',
          name: 'Costco',
          tags: ['supermarket'],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Walgreens'));
    expect(screen.getByText('Costco')).toBeInTheDocument();

    fireEvent.change(screen.getByDisplayValue('All tags'), { target: { value: 'pharmacy' } });

    await waitFor(() => expect(screen.queryByText('Costco')).not.toBeInTheDocument());
    expect(screen.getByText('Walgreens')).toBeInTheDocument();
  });

  it('changes store form fields (notes, address, url)', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
    fireEvent.change(screen.getByPlaceholderText('123 Main St'), {
      target: { value: '456 Oak Ave' },
    });
    fireEvent.change(screen.getByPlaceholderText('Any notes'), {
      target: { value: 'Good prices' },
    });

    expect(screen.getByPlaceholderText('e.g. Walgreens')).toBeInTheDocument();
  });

  it('toggles a preset tag and adds a custom tag', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('Custom tag...'));

    // Toggle a preset tag (e.g. 'pharmacy')
    await userEvent.click(screen.getByRole('button', { name: 'pharmacy' }));
    // Toggle it off again
    await userEvent.click(screen.getByRole('button', { name: 'pharmacy' }));

    // Add a custom tag via button
    fireEvent.change(screen.getByPlaceholderText('Custom tag...'), { target: { value: 'mytag' } });
    await userEvent.click(screen.getByRole('button', { name: 'Add' }));

    await waitFor(() => expect(screen.getByText('mytag')).toBeInTheDocument());

    // Remove it via the × button
    await userEvent.click(screen.getByText('×'));
    expect(screen.queryByText('mytag')).not.toBeInTheDocument();
  });

  it('adds custom tag via Enter key', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Add Store'));
    await userEvent.click(screen.getByText('Add Store'));

    await waitFor(() => screen.getByPlaceholderText('Custom tag...'));
    fireEvent.change(screen.getByPlaceholderText('Custom tag...'), { target: { value: 'keytag' } });
    fireEvent.keyDown(screen.getByPlaceholderText('Custom tag...'), { key: 'Enter' });

    await waitFor(() => expect(screen.getByText('keytag')).toBeInTheDocument());
  });

  it('changes search filter', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Walgreens',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
        {
          _id: 'st-2',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Walgreens'));
    fireEvent.change(screen.getByPlaceholderText('Search stores...'), { target: { value: 'wal' } });

    await waitFor(() => expect(screen.queryByText('CVS')).not.toBeInTheDocument());
    expect(screen.getByText('Walgreens')).toBeInTheDocument();
  });

  it('toggles isActive in edit form', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'CVS',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });
    mockUpdateStore.mockResolvedValue({});

    render(<StoresPage />);

    await waitFor(() => screen.getByText('CVS'));
    await userEvent.click(screen.getByTitle('Edit'));

    await waitFor(() => screen.getByLabelText('Active'));
    fireEvent.click(screen.getByLabelText('Active'));

    expect(screen.getByLabelText('Active')).not.toBeChecked();
  });

  it('shows inactive stores when toggle checked', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockListStores.mockResolvedValue({
      data: [
        {
          _id: 'st-1',
          name: 'Active Store',
          tags: [],
          isActive: true,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
        {
          _id: 'st-2',
          name: 'Old Store',
          tags: [],
          isActive: false,
          createdAt: '2026-01-01T00:00:00.000Z',
          householdId: 'hh1',
          createdBy: 'u-1',
        },
      ],
      pagination: { cursor: null, hasMore: false },
    });

    render(<StoresPage />);

    await waitFor(() => screen.getByText('Active Store'));
    expect(screen.queryByText('Old Store')).not.toBeInTheDocument();

    fireEvent.click(screen.getByLabelText('Show inactive'));

    expect(screen.getByText('Old Store')).toBeInTheDocument();
  });
});
