import { getDevices, getLatestData, getSensorHistory } from '../api';
import axios from 'axios';

// axios 모킹
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

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

  describe('getDevices 오류 처리', () => {
    it('네트워크 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('Network Error'));

      const result = await getDevices();
      expect(result).toBeNull();
    });

    it('서버 오류(500) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 500,
          data: { message: 'Internal Server Error' }
        }
      });

      const result = await getDevices();
      expect(result).toBeNull();
    });

    it('클라이언트 오류(404) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 404,
          data: { message: 'Not Found' }
        }
      });

      const result = await getDevices();
      expect(result).toBeNull();
    });

    it('타임아웃 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('timeout of 5000ms exceeded'));

      const result = await getDevices();
      expect(result).toBeNull();
    });

    it('잘못된 응답 구조 시 null을 반환한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: { invalid_key: 'invalid_data' } // 올바른 구조가 아님
      });

      const result = await getDevices();
      expect(result).toBeNull();
    });

    it('빈 배열 응답 시 빈 배열을 반환한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: []
      });

      const result = await getDevices();
      expect(result).toEqual([]);
    });
  });

  describe('getLatestData 오류 처리', () => {
    it('네트워크 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('Network Error'));

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });

    it('서버 오류(500) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 500,
          data: { message: 'Internal Server Error' }
        }
      });

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });

    it('클라이언트 오류(404) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 404,
          data: { message: 'Not Found' }
        }
      });

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });

    it('타임아웃 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('timeout of 5000ms exceeded'));

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });

    it('빈 디바이스 ID 배열 시 빈 배열을 반환한다', async () => {
      const result = await getLatestData([]);
      expect(result).toEqual([]);
    });

    it('잘못된 응답 구조 시 null을 반환한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: { invalid_key: 'invalid_data' }
      });

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });

    it('필수 필드가 누락된 데이터 시 null을 반환한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            // device_id가 누락됨
            temperature: 25.5,
            humidity: 60.0
          }
        ]
      });

      const result = await getLatestData(['sensor-001']);
      expect(result).toBeNull();
    });
  });

  describe('getSensorHistory 오류 처리', () => {
    it('네트워크 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('Network Error'));

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toBeNull();
    });

    it('서버 오류(500) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 500,
          data: { message: 'Internal Server Error' }
        }
      });

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toBeNull();
    });

    it('클라이언트 오류(404) 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue({
        response: {
          status: 404,
          data: { message: 'Not Found' }
        }
      });

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toBeNull();
    });

    it('타임아웃 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('timeout of 5000ms exceeded'));

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toBeNull();
    });

    it('잘못된 디바이스 ID 시 null을 반환한다', async () => {
      const result = await getSensorHistory('', 'temperature', '1h');
      expect(result).toBeNull();
    });

    it('잘못된 센서 타입 시 null을 반환한다', async () => {
      const result = await getSensorHistory('sensor-001', '', '1h');
      expect(result).toBeNull();
    });

    it('잘못된 시간 범위 시 null을 반환한다', async () => {
      const result = await getSensorHistory('sensor-001', 'temperature', '');
      expect(result).toBeNull();
    });

    it('잘못된 응답 구조 시 null을 반환한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: { invalid_key: 'invalid_data' }
      });

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toBeNull();
    });
  });

  describe('응답 데이터 검증', () => {
    it('getDevices에서 필수 필드가 누락된 디바이스 데이터를 필터링한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            id: 1,
            device_id: 'sensor-001',
            name: '센서 1',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          },
          {
            id: 2,
            // device_id가 누락됨
            name: '센서 2',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          },
          {
            id: 3,
            device_id: 'sensor-003',
            name: '센서 3',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          }
        ]
      });

      const result = await getDevices();
      expect(result).toHaveLength(2); // device_id가 있는 2개만 반환
      expect(result?.[0].device_id).toBe('sensor-001');
      expect(result?.[1].device_id).toBe('sensor-003');
    });

    it('getLatestData에서 필수 필드가 누락된 센서 데이터를 필터링한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            device_id: 'sensor-001',
            temperature: 25.5,
            humidity: 60.0
          },
          {
            // device_id가 누락됨
            temperature: 26.0,
            humidity: 65.0
          },
          {
            device_id: 'sensor-003',
            temperature: 24.5,
            humidity: 55.0
          }
        ]
      });

      const result = await getLatestData(['sensor-001', 'sensor-002', 'sensor-003']);
      expect(result).toHaveLength(2); // device_id가 있는 2개만 반환
      expect(result?.[0].device_id).toBe('sensor-001');
      expect(result?.[1].device_id).toBe('sensor-003');
    });

    it('getSensorHistory에서 필수 필드가 누락된 히스토리 데이터를 필터링한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            timestamp: '2025-08-01T10:00:00Z',
            value: 25.5
          },
          {
            // timestamp가 누락됨
            value: 26.0
          },
          {
            timestamp: '2025-08-01T10:01:00Z',
            value: 24.5
          }
        ]
      });

      const result = await getSensorHistory('sensor-001', 'temperature', '1h');
      expect(result).toHaveLength(2); // timestamp가 있는 2개만 반환
      expect(result?.[0].timestamp).toBe('2025-08-01T10:00:00Z');
      expect(result?.[1].timestamp).toBe('2025-08-01T10:01:00Z');
    });
  });

  describe('타임아웃 및 재시도 로직', () => {
    it('첫 번째 요청이 실패하고 두 번째 요청이 성공하는 경우', async () => {
      mockedAxios.get
        .mockRejectedValueOnce(new Error('Network Error'))
        .mockResolvedValueOnce({
          data: [
            {
              id: 1,
              device_id: 'sensor-001',
              name: '센서 1',
              status: 'active',
              last_seen: '2025-08-01T10:00:00Z'
            }
          ]
        });

      const result = await getDevices();
      expect(result).toHaveLength(1);
      expect(result?.[0].device_id).toBe('sensor-001');
    });

    it('연속적인 네트워크 오류 시 null을 반환한다', async () => {
      mockedAxios.get.mockRejectedValue(new Error('Network Error'));

      const result = await getDevices();
      expect(result).toBeNull();
    });
  });

  describe('데이터 타입 검증', () => {
    it('getDevices에서 잘못된 타입의 데이터를 필터링한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            id: 1,
            device_id: 'sensor-001',
            name: '센서 1',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          },
          {
            id: 'invalid_id', // 숫자가 아닌 ID
            device_id: 'sensor-002',
            name: '센서 2',
            status: 'active',
            last_seen: '2025-08-01T10:00:00Z'
          }
        ]
      });

      const result = await getDevices();
      expect(result).toHaveLength(1); // 유효한 타입의 데이터만 반환
      expect(result?.[0].id).toBe(1);
    });

    it('getLatestData에서 잘못된 타입의 센서 값을 처리한다', async () => {
      mockedAxios.get.mockResolvedValue({
        data: [
          {
            device_id: 'sensor-001',
            temperature: '25.5', // 문자열
            humidity: 60.0,
            pm10: null,
            pm25: undefined
          }
        ]
      });

      const result = await getLatestData(['sensor-001']);
      expect(result).toHaveLength(1);
      expect(result?.[0].device_id).toBe('sensor-001');
      // 타입 변환은 프론트엔드에서 처리되므로 여기서는 원본 데이터 반환
    });
  });
}); 