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

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

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

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

vi.mock('@/services/recipes', () => ({
  getRecipe: mockGetRecipe,
  scaleRecipe: mockScaleRecipe,
  listRecipes: vi.fn(),
  deleteRecipe: vi.fn(),
  createRecipe: vi.fn(),
  updateRecipe: vi.fn(),
  importRecipeFromText: vi.fn(),
  importRecipeFromUrl: vi.fn(),
  listRecipesByProduct: vi.fn(),
}));

vi.mock('next/navigation', () => ({
  useParams: () => ({ id: 'r1' }),
  useRouter: () => ({ push: vi.fn(), back: vi.fn() }),
}));

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

import RecipeDetailPage from '../../../../../src/app/(dashboard)/recipes/[id]/page';

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

const SAMPLE_RECIPE = {
  _id: 'r1',
  householdId: 'hh1',
  name: 'Spaghetti Bolognese',
  description: 'Classic Italian pasta',
  servings: 4,
  prepTime: 15,
  cookTime: 30,
  totalTime: 45,
  cuisine: 'Italian',
  tags: ['pasta'],
  isFavorite: true,
  ingredients: [
    {
      productId: 'p1',
      productName: 'Spaghetti',
      quantity: 400,
      unit: 'g',
      isOptional: false,
    },
    {
      productId: 'p2',
      productName: 'Parmesan',
      quantity: 50,
      unit: 'g',
      isOptional: true,
      preparation: 'grated',
    },
  ],
  steps: [
    { order: 1, instruction: 'Boil water', duration: 10 },
    { order: 2, instruction: 'Cook pasta', tip: 'Al dente' },
  ],
  perServingNutrition: {
    calories: 450,
    protein: 25,
    carbs: 55,
    fat: 12,
    fiber: 3,
    sugar: 5,
    sodium: 400,
    saturatedFat: 4,
    cholesterol: 50,
  },
  totalNutrition: {
    calories: 1800,
    protein: 100,
    carbs: 220,
    fat: 48,
  },
  warnings: ['high_calories'],
  createdBy: 'u1',
  createdAt: '2026-01-01T00:00:00.000Z',
  updatedAt: '2026-01-01T00:00:00.000Z',
};

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

    render(<RecipeDetailPage />);

    expect(screen.getByText('Recipe')).toBeInTheDocument();
  });

  it('shows error when fetch fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockRejectedValue(new Error('Not found'));

    render(<RecipeDetailPage />);

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

  it('shows recipe not found fallback', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    // Resolve with null-like to trigger error path
    mockGetRecipe.mockRejectedValue(new Error('Recipe not found'));

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Recipe not found')).toBeInTheDocument();
    });
  });

  it('renders recipe details', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Spaghetti Bolognese')).toBeInTheDocument();
      expect(screen.getByText('Classic Italian pasta')).toBeInTheDocument();
      expect(screen.getByText('4 servings')).toBeInTheDocument();
      expect(screen.getByText('Starred')).toBeInTheDocument();
    });
  });

  it('shows ingredients with optional indicator', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Spaghetti')).toBeInTheDocument();
      expect(screen.getByText('Parmesan')).toBeInTheDocument();
      expect(screen.getByText('(optional)')).toBeInTheDocument();
    });
  });

  it('shows steps with duration and tips', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Boil water')).toBeInTheDocument();
      expect(screen.getByText('Cook pasta')).toBeInTheDocument();
      expect(screen.getByText('Tip: Al dente')).toBeInTheDocument();
      expect(screen.getByText('10 min')).toBeInTheDocument();
    });
  });

  it('shows nutritional warnings', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

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

  it('shows nutrition panel', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('450 kcal')).toBeInTheDocument();
      expect(screen.getByText('25.0g')).toBeInTheDocument();
    });
  });

  it('shows edit link', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);

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

  it('handles non-Error fetch failure', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockRejectedValue('string error');

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Failed to load recipe')).toBeInTheDocument();
    });
  });

  it('renders recipe without description', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue({
      ...SAMPLE_RECIPE,
      description: undefined,
      warnings: [],
      isFavorite: false,
    });

    render(<RecipeDetailPage />);

    await waitFor(() => {
      expect(screen.getByText('Spaghetti Bolognese')).toBeInTheDocument();
      expect(screen.queryByText('Starred')).not.toBeInTheDocument();
    });
  });

  it('scales the recipe servings', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);
    mockScaleRecipe.mockResolvedValue({
      ...SAMPLE_RECIPE,
      servings: 8,
      ingredients: SAMPLE_RECIPE.ingredients.map(i => ({ ...i, quantity: i.quantity * 2 })),
    });

    render(<RecipeDetailPage />);
    await screen.findByText('Spaghetti Bolognese');

    const scaleInput = screen.getByRole('spinbutton');
    fireEvent.change(scaleInput, { target: { value: '8' } });

    const scaleBtn = screen.getByRole('button', { name: /Scale/i });
    fireEvent.click(scaleBtn);

    await waitFor(() => {
      expect(mockScaleRecipe).toHaveBeenCalledWith('hh1', 'r1', { targetServings: 8 });
    });

    // Test resetting back to original
    const resetBtn = screen.getByRole('button', { name: /Reset to original/i });
    fireEvent.click(resetBtn);
    expect((scaleInput as HTMLInputElement).value).toBe('4');
  });

  it('handles scale recipe failure gracefully', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);
    mockScaleRecipe.mockRejectedValue(new Error('Scale failed'));

    render(<RecipeDetailPage />);
    await screen.findByText('Spaghetti Bolognese');

    const scaleInput = screen.getByRole('spinbutton');
    fireEvent.change(scaleInput, { target: { value: '6' } });

    const scaleBtn = screen.getByRole('button', { name: /Scale/i });
    fireEvent.click(scaleBtn);

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

  it('skips scaling if servings did not change', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
    mockGetRecipe.mockResolvedValue(SAMPLE_RECIPE);

    render(<RecipeDetailPage />);
    await screen.findByText('Spaghetti Bolognese');

    const scaleBtn = screen.getByRole('button', { name: /Scale/i });
    fireEvent.click(scaleBtn);

    expect(mockScaleRecipe).not.toHaveBeenCalled();
  });
});
