// src/pages/admin/components/AdminBrandManager.test.tsx
import React from 'react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import toast from 'react-hot-toast';
import { AdminBrandManager } from './AdminBrandManager';
import * as apiClient from '../../../services/apiClient';
import { useBrandsQuery } from '../../../hooks/queries/useBrandsQuery';
import { createMockBrand } from '../../../tests/utils/mockFactories';
import { renderWithProviders } from '../../../tests/utils/renderWithProviders';

// Must explicitly call vi.mock() for apiClient and the hook
vi.mock('../../../services/apiClient');
vi.mock('../../../hooks/queries/useBrandsQuery');

const mockedApiClient = vi.mocked(apiClient);
const mockedUseBrandsQuery = vi.mocked(useBrandsQuery);
const mockedToast = vi.mocked(toast, true);

const mockBrands = [
  createMockBrand({ brand_id: 1, name: 'No Frills', store_name: 'No Frills', logo_url: null }),
  createMockBrand({
    brand_id: 2,
    name: 'Compliments',
    store_name: 'Sobeys',
    logo_url: 'https://example.com/compliments.png',
  }),
];

describe('AdminBrandManager', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Default mock: loading false, empty data
    mockedUseBrandsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
      refetch: vi.fn(),
    } as any);
  });

  it('should render a loading state initially', () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: undefined,
      isLoading: true,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);

    expect(screen.getByText('Loading brands...')).toBeInTheDocument();
  });

  it('should render an error message if fetching brands fails', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: undefined,
      isLoading: false,
      error: new Error('Network Error'),
    } as any);

    renderWithProviders(<AdminBrandManager />);

    await waitFor(() => {
      expect(screen.getByText('Failed to load brands: Network Error')).toBeInTheDocument();
    });
  });

  it('should render the list of brands when data is fetched successfully', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);

    await waitFor(() => {
      expect(screen.getByRole('heading', { name: /brand management/i })).toBeInTheDocument();
      expect(screen.getByText('No Frills')).toBeInTheDocument();
      expect(screen.getByText('(Sobeys)')).toBeInTheDocument();
      expect(screen.getByAltText('Compliments logo')).toBeInTheDocument();
      expect(screen.getByText('No Logo')).toBeInTheDocument();
    });
  });

  it('should handle successful logo upload', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockImplementation(
      async () =>
        new Response(JSON.stringify({ logoUrl: 'https://example.com/new-logo.png' }), {
          status: 200,
        }),
    );
    mockedToast.loading.mockReturnValue('toast-1');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedApiClient.uploadBrandLogo).toHaveBeenCalledWith(1, file);
      expect(mockedToast.loading).toHaveBeenCalledWith('Uploading logo...');
      expect(mockedToast.success).toHaveBeenCalledWith('Logo updated successfully!', {
        id: 'toast-1',
      });
      expect(screen.getByAltText('No Frills logo')).toHaveAttribute(
        'src',
        'https://example.com/new-logo.png',
      );
    });
  });

  it('should handle failed logo upload with a non-Error object', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockRejectedValue('A string error');
    mockedToast.loading.mockReturnValue('toast-non-error');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith('Upload failed: A string error', {
        id: 'toast-non-error',
      });
    });
  });

  it('should handle failed logo upload', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockRejectedValue(new Error('Upload failed'));
    mockedToast.loading.mockReturnValue('toast-2');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith('Upload failed: Upload failed', {
        id: 'toast-2',
      });
    });
  });

  it('should show an error toast for invalid file type', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['text'], 'document.txt', { type: 'text/plain' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith(
        'Invalid file type. Please upload a PNG, JPG, WEBP, or SVG.',
      );
      expect(mockedApiClient.uploadBrandLogo).not.toHaveBeenCalled();
    });
  });

  it('should show an error toast for oversized file', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['a'.repeat(3 * 1024 * 1024)], 'large.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith('File is too large. Maximum size is 2MB.');
      expect(mockedApiClient.uploadBrandLogo).not.toHaveBeenCalled();
    });
  });

  it('should show an error toast if upload fails with a non-ok response', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockResolvedValue(
      new Response('Invalid image format', { status: 400 }),
    );
    mockedToast.loading.mockReturnValue('toast-3');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');

    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith('Upload failed: Invalid image format', {
        id: 'toast-3',
      });
    });
  });

  it('should show an error toast if no file is selected', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const input = screen.getByLabelText('Upload logo for No Frills');
    fireEvent.change(input, { target: { files: [] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith('Please select a file to upload.');
    });
  });

  it('should render an empty table if no brands are found', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
    } as any);

    renderWithProviders(<AdminBrandManager />);

    await waitFor(() => {
      expect(screen.getByRole('heading', { name: /brand management/i })).toBeInTheDocument();
      expect(screen.getAllByRole('row')).toHaveLength(1);
    });
  });

  it('should use status code in error message if response body is empty on upload failure', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockImplementation(
      async () => new Response(null, { status: 500, statusText: 'Internal Server Error' }),
    );
    mockedToast.loading.mockReturnValue('toast-fallback');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');
    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(mockedToast.error).toHaveBeenCalledWith(
        'Upload failed: Upload failed with status 500',
        { id: 'toast-fallback' },
      );
    });
  });

  it('should only update the target brand logo and leave others unchanged', async () => {
    mockedUseBrandsQuery.mockReturnValue({
      data: mockBrands,
      isLoading: false,
      error: null,
    } as any);

    mockedApiClient.uploadBrandLogo.mockImplementation(
      async () => new Response(JSON.stringify({ logoUrl: 'new-logo.png' }), { status: 200 }),
    );
    mockedToast.loading.mockReturnValue('toast-opt');

    renderWithProviders(<AdminBrandManager />);
    await waitFor(() => expect(screen.getByText('No Frills')).toBeInTheDocument());

    const file = new File(['logo'], 'logo.png', { type: 'image/png' });
    const input = screen.getByLabelText('Upload logo for No Frills');
    fireEvent.change(input, { target: { files: [file] } });

    await waitFor(() => {
      expect(screen.getByAltText('No Frills logo')).toHaveAttribute('src', 'new-logo.png');
      expect(screen.getByAltText('Compliments logo')).toHaveAttribute(
        'src',
        'https://example.com/compliments.png',
      );
    });
  });
});
