import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import Dashboard from '../pages/Dashboard';
import { useDevices } from '../hooks/useDevices';
import { useSensorData } from '../hooks/useSensorData';
import { getDevices, getLatestData } from '../services/api';

// Mock the hooks and API services
jest.mock('../hooks/useDevices');
jest.mock('../hooks/useSensorData');
jest.mock('../services/api');

const mockUseDevices = useDevices as jest.MockedFunction<typeof useDevices>;
const mockUseSensorData = useSensorData as jest.MockedFunction<typeof useSensorData>;
const mockGetDevices = getDevices as jest.MockedFunction<typeof getDevices>;
const mockGetLatestData = getLatestData as jest.MockedFunction<typeof getLatestData>;

// Mock Recharts components
jest.mock('recharts', () => ({
  LineChart: ({ children, data }: any) => (
    <div data-testid="line-chart" data-chart-data={JSON.stringify(data)}>
      {children}
    </div>
  ),
  Line: ({ dataKey }: any) => (
    <div data-testid={`line-${dataKey}`}>Line: {dataKey}</div>
  ),
  XAxis: () => <div data-testid="x-axis">XAxis</div>,
  YAxis: () => <div data-testid="y-axis">YAxis</div>,
  CartesianGrid: () => <div data-testid="cartesian-grid">CartesianGrid</div>,
  Tooltip: () => <div data-testid="tooltip">Tooltip</div>,
  Legend: () => <div data-testid="legend">Legend</div>,
  ResponsiveContainer: ({ children }: any) => (
    <div data-testid="responsive-container">{children}</div>
  ),
}));

const renderWithRouter = (component: React.ReactElement) => {
  return render(
    <BrowserRouter>
      {component}
    </BrowserRouter>
  );
};

describe('API 오류 시나리오 테스트', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('네트워크 오류 시나리오', () => {
    it('네트워크 연결 오류 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '네트워크 연결을 확인해주세요.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('네트워크 연결을 확인해주세요.')).toBeInTheDocument();
      expect(screen.getByText('다시 시도')).toBeInTheDocument();
    });

    it('서버 연결 타임아웃 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '서버 응답 시간이 초과되었습니다.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('서버 응답 시간이 초과되었습니다.')).toBeInTheDocument();
    });

    it('센서 데이터 로딩 실패 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [{ id: 1, device_id: 'sensor-001', name: '테스트 센서', status: 'active', last_seen: '2025-08-01T10:00:00Z' }],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: '센서 데이터를 불러올 수 없습니다.',
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('센서 데이터를 불러올 수 없습니다.')).toBeInTheDocument();
    });
  });

  describe('서버 오류 시나리오', () => {
    it('500 서버 오류 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '서버 내부 오류가 발생했습니다. (500)',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('서버 내부 오류가 발생했습니다. (500)')).toBeInTheDocument();
    });

    it('404 리소스 없음 오류 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '요청한 리소스를 찾을 수 없습니다. (404)',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('요청한 리소스를 찾을 수 없습니다. (404)')).toBeInTheDocument();
    });

    it('403 권한 없음 오류 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '접근 권한이 없습니다. (403)',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('접근 권한이 없습니다. (403)')).toBeInTheDocument();
    });
  });

  describe('데이터 무결성 오류 시나리오', () => {
    it('잘못된 JSON 응답 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '서버에서 잘못된 데이터 형식을 반환했습니다.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('서버에서 잘못된 데이터 형식을 반환했습니다.')).toBeInTheDocument();
    });

    it('필수 필드 누락 시 적절한 오류 메시지를 표시한다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '응답 데이터에 필수 필드가 누락되었습니다.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('응답 데이터에 필수 필드가 누락되었습니다.')).toBeInTheDocument();
    });
  });

  describe('재시도 기능 테스트', () => {
    it('다시 시도 버튼 클릭 시 refetch 함수가 호출된다', async () => {
      const mockRefetchDevices = jest.fn();
      const mockRefetchData = jest.fn();

      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '디바이스 목록을 불러올 수 없습니다.',
        refetch: mockRefetchDevices
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: '센서 데이터를 불러올 수 없습니다.',
        refetch: mockRefetchData
      });

      renderWithRouter(<Dashboard />);

      const retryButton = screen.getByText('다시 시도');
      fireEvent.click(retryButton);

      expect(mockRefetchDevices).toHaveBeenCalled();
      expect(mockRefetchData).toHaveBeenCalled();
    });

    it('재시도 후 로딩 상태가 표시된다', async () => {
      const mockRefetchDevices = jest.fn();
      const mockRefetchData = jest.fn();

      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '디바이스 목록을 불러올 수 없습니다.',
        refetch: mockRefetchDevices
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: '센서 데이터를 불러올 수 없습니다.',
        refetch: mockRefetchData
      });

      renderWithRouter(<Dashboard />);

      const retryButton = screen.getByText('다시 시도');
      fireEvent.click(retryButton);

      // 재시도 후 로딩 상태로 변경
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: true,
        error: null,
        refetch: mockRefetchDevices
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: true,
        error: null,
        refetch: mockRefetchData
      });

      // 컴포넌트를 다시 렌더링하여 로딩 상태 확인
      const { rerender } = renderWithRouter(<Dashboard />);
      
      // 로딩 상태로 변경
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: true,
        error: null,
        refetch: mockRefetchDevices
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: true,
        error: null,
        refetch: mockRefetchData
      });

      rerender(
        <BrowserRouter>
          <Dashboard />
        </BrowserRouter>
      );

      expect(screen.getByText('데이터를 불러오는 중...')).toBeInTheDocument();
    });
  });

  describe('부분적 오류 시나리오', () => {
    it('디바이스 목록만 오류가 발생한 경우 센서 데이터는 정상 표시된다', () => {
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '디바이스 목록을 불러올 수 없습니다.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [
          {
            device_id: 'sensor-001',
            temperature: 25.5,
            humidity: 60.0,
            pm10: 15.2,
            pm25: 8.1,
            pressure: 1013.25,
            illumination: 500,
            tvoc: 120,
            co2: 450,
            o2: 20.9,
            co: 0.5,
            recorded_time: '2025-08-01T10:00:00Z'
          }
        ],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      // 오류 메시지가 표시되지만 센서 데이터도 함께 표시됨
      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('25.5°C')).toBeInTheDocument();
      expect(screen.getByText('60.0%')).toBeInTheDocument();
    });

    it('센서 데이터만 오류가 발생한 경우 디바이스 목록은 정상 표시된다', () => {
      mockUseDevices.mockReturnValue({
        devices: [
          {
            id: 1,
            device_id: 'sensor-001',
            name: '테스트 센서',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          }
        ],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: '센서 데이터를 불러올 수 없습니다.',
        refetch: jest.fn()
      });

      renderWithRouter(<Dashboard />);

      // 오류 메시지가 표시되지만 디바이스 정보도 함께 표시됨
      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();
      expect(screen.getByText('센서 데이터를 불러올 수 없습니다.')).toBeInTheDocument();
      expect(screen.getByText('디바이스 상태')).toBeInTheDocument();
    });
  });

  describe('오류 복구 시나리오', () => {
    it('오류 후 정상 데이터가 로드되면 오류 메시지가 사라진다', async () => {
      // 초기 오류 상태
      mockUseDevices.mockReturnValue({
        devices: [],
        loading: false,
        error: '디바이스 목록을 불러올 수 없습니다.',
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      const { rerender } = renderWithRouter(<Dashboard />);

      expect(screen.getByText('오류가 발생했습니다')).toBeInTheDocument();

      // 정상 데이터로 복구
      mockUseDevices.mockReturnValue({
        devices: [
          {
            id: 1,
            device_id: 'sensor-001',
            name: '테스트 센서',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          }
        ],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      mockUseSensorData.mockReturnValue({
        latestData: [
          {
            device_id: 'sensor-001',
            temperature: 25.5,
            humidity: 60.0,
            pm10: 15.2,
            pm25: 8.1,
            pressure: 1013.25,
            illumination: 500,
            tvoc: 120,
            co2: 450,
            o2: 20.9,
            co: 0.5,
            recorded_time: '2025-08-01T10:00:00Z'
          }
        ],
        loading: false,
        error: null,
        refetch: jest.fn()
      });

      rerender(
        <BrowserRouter>
          <Dashboard />
        </BrowserRouter>
      );

      // 오류 메시지가 사라지고 정상 데이터가 표시됨
      await waitFor(() => {
        expect(screen.queryByText('오류가 발생했습니다')).not.toBeInTheDocument();
        expect(screen.getByText('25.5°C')).toBeInTheDocument();
        expect(screen.getByText('60.0%')).toBeInTheDocument();
      });
    });
  });
}); 