// __tests__/openweather.test.js
const OpenWeather = require('../OpenWeather');
const fetch = require('node-fetch');

// Mock environment variable
process.env.OPENWEATHER_API_KEY = 'test-api-key';

// Mock the fetch function globally
jest.mock('node-fetch', () => jest.fn());

describe('OpenWeather Tool', () => {
  let tool;

  beforeAll(() => {
    tool = new OpenWeather();
  });

  beforeEach(() => {
    fetch.mockReset();
  });

  test('action=help returns help instructions', async () => {
    const result = await tool.call({
      action: 'help',
    });

    expect(typeof result).toBe('string');
    const parsed = JSON.parse(result);
    expect(parsed.title).toBe('OpenWeather One Call API 3.0 Help');
  });

  test('current_forecast with a city and successful geocoding + forecast', async () => {
    // Mock geocoding response
    fetch.mockImplementationOnce((url) => {
      if (url.includes('geo/1.0/direct')) {
        return Promise.resolve({
          ok: true,
          json: async () => [{ lat: 35.9606, lon: -83.9207 }],
        });
      }
      return Promise.reject('Unexpected fetch call for geocoding');
    });

    // Mock forecast response
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => ({
          current: { temp: 293.15, feels_like: 295.15 },
          daily: [{ temp: { day: 293.15, night: 283.15 } }],
        }),
      }),
    );

    const result = await tool.call({
      action: 'current_forecast',
      city: 'Knoxville, Tennessee',
      units: 'Kelvin',
    });

    const parsed = JSON.parse(result);
    expect(parsed.current.temp).toBe(293);
    expect(parsed.current.feels_like).toBe(295);
    expect(parsed.daily[0].temp.day).toBe(293);
    expect(parsed.daily[0].temp.night).toBe(283);
  });

  test('timestamp action with valid date returns mocked historical data', async () => {
    // Mock geocoding response
    fetch.mockImplementationOnce((url) => {
      if (url.includes('geo/1.0/direct')) {
        return Promise.resolve({
          ok: true,
          json: async () => [{ lat: 35.9606, lon: -83.9207 }],
        });
      }
      return Promise.reject('Unexpected fetch call for geocoding');
    });

    // Mock historical weather response
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => ({
          data: [
            {
              dt: 1583280000,
              temp: 283.15,
              feels_like: 280.15,
              humidity: 75,
              weather: [{ description: 'clear sky' }],
            },
          ],
        }),
      }),
    );

    const result = await tool.call({
      action: 'timestamp',
      city: 'Knoxville, Tennessee',
      date: '2020-03-04',
      units: 'Kelvin',
    });

    const parsed = JSON.parse(result);
    expect(parsed.data[0].temp).toBe(283);
    expect(parsed.data[0].feels_like).toBe(280);
  });

  test('daily_aggregation action returns aggregated weather data', async () => {
    // Mock geocoding response
    fetch.mockImplementationOnce((url) => {
      if (url.includes('geo/1.0/direct')) {
        return Promise.resolve({
          ok: true,
          json: async () => [{ lat: 35.9606, lon: -83.9207 }],
        });
      }
      return Promise.reject('Unexpected fetch call for geocoding');
    });

    // Mock daily aggregation response
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => ({
          date: '2020-03-04',
          temperature: {
            morning: 283.15,
            afternoon: 293.15,
            evening: 288.15,
          },
          humidity: {
            morning: 75,
            afternoon: 60,
            evening: 70,
          },
        }),
      }),
    );

    const result = await tool.call({
      action: 'daily_aggregation',
      city: 'Knoxville, Tennessee',
      date: '2020-03-04',
      units: 'Kelvin',
    });

    const parsed = JSON.parse(result);
    expect(parsed.temperature.morning).toBe(283);
    expect(parsed.temperature.afternoon).toBe(293);
    expect(parsed.temperature.evening).toBe(288);
  });

  test('overview action returns weather summary', async () => {
    // Mock geocoding response
    fetch.mockImplementationOnce((url) => {
      if (url.includes('geo/1.0/direct')) {
        return Promise.resolve({
          ok: true,
          json: async () => [{ lat: 35.9606, lon: -83.9207 }],
        });
      }
      return Promise.reject('Unexpected fetch call for geocoding');
    });

    // Mock overview response
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => ({
          date: '2024-01-07',
          lat: 35.9606,
          lon: -83.9207,
          tz: '+00:00',
          units: 'metric',
          weather_overview:
            'Currently, the temperature is 2°C with a real feel of -2°C. The sky is clear with moderate wind.',
        }),
      }),
    );

    const result = await tool.call({
      action: 'overview',
      city: 'Knoxville, Tennessee',
      units: 'Celsius',
    });

    const parsed = JSON.parse(result);
    expect(parsed).toHaveProperty('weather_overview');
    expect(typeof parsed.weather_overview).toBe('string');
    expect(parsed.weather_overview.length).toBeGreaterThan(0);
    expect(parsed).toHaveProperty('date');
    expect(parsed).toHaveProperty('units');
    expect(parsed.units).toBe('metric');
  });

  test('temperature units are correctly converted', async () => {
    // Mock geocoding response for all three calls
    const geocodingMock = Promise.resolve({
      ok: true,
      json: async () => [{ lat: 35.9606, lon: -83.9207 }],
    });

    // Mock weather response for Kelvin
    const kelvinMock = Promise.resolve({
      ok: true,
      json: async () => ({
        current: { temp: 293.15 },
      }),
    });

    // Mock weather response for Celsius
    const celsiusMock = Promise.resolve({
      ok: true,
      json: async () => ({
        current: { temp: 20 },
      }),
    });

    // Mock weather response for Fahrenheit
    const fahrenheitMock = Promise.resolve({
      ok: true,
      json: async () => ({
        current: { temp: 68 },
      }),
    });

    // Test Kelvin
    fetch.mockImplementationOnce(() => geocodingMock).mockImplementationOnce(() => kelvinMock);

    let result = await tool.call({
      action: 'current_forecast',
      city: 'Knoxville, Tennessee',
      units: 'Kelvin',
    });
    let parsed = JSON.parse(result);
    expect(parsed.current.temp).toBe(293);

    // Test Celsius
    fetch.mockImplementationOnce(() => geocodingMock).mockImplementationOnce(() => celsiusMock);

    result = await tool.call({
      action: 'current_forecast',
      city: 'Knoxville, Tennessee',
      units: 'Celsius',
    });
    parsed = JSON.parse(result);
    expect(parsed.current.temp).toBe(20);

    // Test Fahrenheit
    fetch.mockImplementationOnce(() => geocodingMock).mockImplementationOnce(() => fahrenheitMock);

    result = await tool.call({
      action: 'current_forecast',
      city: 'Knoxville, Tennessee',
      units: 'Fahrenheit',
    });
    parsed = JSON.parse(result);
    expect(parsed.current.temp).toBe(68);
  });

  test('timestamp action without a date returns an error message', async () => {
    const result = await tool.call({
      action: 'timestamp',
      lat: 35.9606,
      lon: -83.9207,
    });
    expect(result).toMatch(
      /Error: For timestamp action, a 'date' in YYYY-MM-DD format is required./,
    );
  });

  test('daily_aggregation action without a date returns an error message', async () => {
    const result = await tool.call({
      action: 'daily_aggregation',
      lat: 35.9606,
      lon: -83.9207,
    });
    expect(result).toMatch(/Error: date \(YYYY-MM-DD\) is required for daily_aggregation action./);
  });

  test('unknown action returns an error due to schema validation', async () => {
    await expect(
      tool.call({
        action: 'unknown_action',
      }),
    ).rejects.toThrow(/Received tool input did not match expected schema/);
  });

  test('geocoding failure returns a descriptive error', async () => {
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => [],
      }),
    );

    const result = await tool.call({
      action: 'current_forecast',
      city: 'NowhereCity',
    });
    expect(result).toMatch(/Error: Could not find coordinates for city: NowhereCity/);
  });

  test('API request failure returns an error', async () => {
    // Mock geocoding success
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: true,
        json: async () => [{ lat: 35.9606, lon: -83.9207 }],
      }),
    );

    // Mock weather request failure
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        ok: false,
        status: 404,
        json: async () => ({ message: 'Not found' }),
      }),
    );

    const result = await tool.call({
      action: 'current_forecast',
      city: 'Knoxville, Tennessee',
    });
    expect(result).toMatch(/Error: OpenWeather API request failed with status 404: Not found/);
  });

  test('invalid date format returns an error', async () => {
    // Mock geocoding response first
    fetch.mockImplementationOnce((url) => {
      if (url.includes('geo/1.0/direct')) {
        return Promise.resolve({
          ok: true,
          json: async () => [{ lat: 35.9606, lon: -83.9207 }],
        });
      }
      return Promise.reject('Unexpected fetch call for geocoding');
    });

    // Mock timestamp API response
    fetch.mockImplementationOnce((url) => {
      if (url.includes('onecall/timemachine')) {
        throw new Error('Invalid date format. Expected YYYY-MM-DD.');
      }
      return Promise.reject('Unexpected fetch call');
    });

    const result = await tool.call({
      action: 'timestamp',
      city: 'Knoxville, Tennessee',
      date: '03-04-2020', // Wrong format
    });
    expect(result).toMatch(/Error: Invalid date format. Expected YYYY-MM-DD./);
  });
});
