import AsyncStorage from '@react-native-async-storage/async-storage';
import { StorageService } from '../../services/storageService';
import { Plant } from '../../types';

jest.mock('@react-native-async-storage/async-storage', () => ({
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
}));

const mockPlant: Plant = {
  id: '1',
  name: 'Monstera',
  botanicalName: 'Monstera deliciosa',
  imageUri: 'https://example.com/img.jpg',
  dateAdded: '2024-01-01',
  careInfo: { waterIntervalDays: 7, light: 'Partial Shade', temp: '18-24°C' },
  lastWatered: '2024-01-01',
};

beforeEach(() => {
  jest.clearAllMocks();
});

describe('StorageService', () => {
  describe('getPlants', () => {
    it('returns empty array when no data stored', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(null);
      const result = await StorageService.getPlants();
      expect(result).toEqual([]);
    });

    it('returns parsed plants when data exists', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify([mockPlant]));
      const result = await StorageService.getPlants();
      expect(result).toEqual([mockPlant]);
    });

    it('returns empty array on error', async () => {
      (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('fail'));
      const result = await StorageService.getPlants();
      expect(result).toEqual([]);
    });
  });

  describe('savePlant', () => {
    it('prepends plant to existing list', async () => {
      const existing: Plant = { ...mockPlant, id: '2', name: 'Ficus' };
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify([existing]));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      await StorageService.savePlant(mockPlant);

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_plants',
        JSON.stringify([mockPlant, existing])
      );
    });
  });

  describe('deletePlant', () => {
    it('removes plant by id', async () => {
      const plant2: Plant = { ...mockPlant, id: '2' };
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify([mockPlant, plant2]));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      await StorageService.deletePlant('1');

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_plants',
        JSON.stringify([plant2])
      );
    });
  });

  describe('updatePlant', () => {
    it('updates existing plant', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify([mockPlant]));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      const updated = { ...mockPlant, name: 'Updated Monstera' };
      await StorageService.updatePlant(updated);

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_plants',
        JSON.stringify([updated])
      );
    });

    it('does nothing if plant not found', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify([mockPlant]));

      await StorageService.updatePlant({ ...mockPlant, id: 'nonexistent' });

      expect(AsyncStorage.setItem).not.toHaveBeenCalled();
    });
  });

  describe('getLanguage', () => {
    it('returns stored language', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('en');
      const result = await StorageService.getLanguage();
      expect(result).toBe('en');
    });

    it('defaults to de when no language stored', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(null);
      const result = await StorageService.getLanguage();
      expect(result).toBe('de');
    });

    it('defaults to de on error', async () => {
      (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('fail'));
      const result = await StorageService.getLanguage();
      expect(result).toBe('de');
    });
  });

  describe('saveLanguage', () => {
    it('stores language', async () => {
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);
      await StorageService.saveLanguage('es');
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('greenlens_language', 'es');
    });
  });

  describe('getAppearanceMode', () => {
    it('returns stored appearance mode', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('dark');
      const result = await StorageService.getAppearanceMode();
      expect(result).toBe('dark');
    });

    it('defaults to system when value is invalid', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('invalid');
      const result = await StorageService.getAppearanceMode();
      expect(result).toBe('system');
    });
  });

  describe('saveAppearanceMode', () => {
    it('stores appearance mode', async () => {
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);
      await StorageService.saveAppearanceMode('light');
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('greenlens_appearance_mode', 'light');
    });
  });

  describe('getColorPalette', () => {
    it('returns stored palette', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('ocean');
      const result = await StorageService.getColorPalette();
      expect(result).toBe('ocean');
    });

    it('defaults to forest when value is invalid', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('invalid');
      const result = await StorageService.getColorPalette();
      expect(result).toBe('forest');
    });
  });

  describe('saveColorPalette', () => {
    it('stores palette', async () => {
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);
      await StorageService.saveColorPalette('sunset');
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('greenlens_color_palette', 'sunset');
    });
  });

  describe('profile name', () => {
    it('returns stored profile name', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('Taylor');
      const result = await StorageService.getProfileName();
      expect(result).toBe('Taylor');
    });

    it('falls back to default profile name when empty', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('   ');
      const result = await StorageService.getProfileName();
      expect(result).toBe('Alex Rivera');
    });

    it('stores normalized profile name', async () => {
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);
      await StorageService.saveProfileName('  Morgan  ');
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('greenlens_profile_name', 'Morgan');
    });
  });

  describe('lexicon search history', () => {
    it('returns empty history when not set', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(null);
      const result = await StorageService.getLexiconSearchHistory();
      expect(result).toEqual([]);
    });

    it('returns parsed history entries', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify(['Monstera', 'Aloe']));
      const result = await StorageService.getLexiconSearchHistory();
      expect(result).toEqual(['Monstera', 'Aloe']);
    });

    it('returns empty history on malformed JSON', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('{bad json}');
      const result = await StorageService.getLexiconSearchHistory();
      expect(result).toEqual([]);
    });

    it('saves new query at front', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify(['Monstera', 'Aloe']));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      await StorageService.saveLexiconSearchQuery('Ficus');

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_lexicon_search_history',
        JSON.stringify(['Ficus', 'Monstera', 'Aloe'])
      );
    });

    it('deduplicates case and diacritic variants by moving entry to front', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify(['Monstera', 'Aloe']));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      await StorageService.saveLexiconSearchQuery('monstera');

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_lexicon_search_history',
        JSON.stringify(['monstera', 'Aloe'])
      );
    });

    it('ignores empty queries', async () => {
      await StorageService.saveLexiconSearchQuery('   ');
      expect(AsyncStorage.setItem).not.toHaveBeenCalled();
    });

    it('limits history to 10 entries', async () => {
      const history = ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'];
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(JSON.stringify(history));
      (AsyncStorage.setItem as jest.Mock).mockResolvedValue(undefined);

      await StorageService.saveLexiconSearchQuery('q11');

      expect(AsyncStorage.setItem).toHaveBeenCalledWith(
        'greenlens_lexicon_search_history',
        JSON.stringify(['q11', 'q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9'])
      );
    });

    it('clears history', async () => {
      (AsyncStorage.removeItem as jest.Mock).mockResolvedValue(undefined);
      await StorageService.clearLexiconSearchHistory();
      expect(AsyncStorage.removeItem).toHaveBeenCalledWith('greenlens_lexicon_search_history');
    });
  });
});
