/**
 * Tests for State Versioning
 * FE-STATE-011: Test versioning and migration support
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
  applyMigrations,
  versionState,
  unversionState,
  createVersionedStorage,
  createMigration,
  createMigrations,
  type VersionedState,
} from './stateVersioning';

interface TestStateV1 {
  count: number;
  name: string;
}

interface TestStateV2 {
  count: number;
  name: string;
  metadata: {
    createdAt: string;
  };
}

interface TestStateV3 {
  count: number;
  name: string;
  metadata: {
    createdAt: string;
    updatedAt: string;
  };
}

describe('stateVersioning', () => {
  beforeEach(() => {
    // Clear localStorage before each test
    if (typeof window !== 'undefined') {
      localStorage.clear();
    }
  });

  describe('versionState', () => {
    it('should wrap state with version', () => {
      const state = { count: 5, name: 'test' };
      const versioned = versionState(state, 2);

      expect(versioned.version).toBe(2);
      expect(versioned.state).toEqual(state);
      expect(versioned.metadata?.migratedAt).toBeDefined();
    });

    it('should preserve existing metadata', () => {
      const state = { count: 5 };
      const metadata = { migratedFrom: 1 };
      const versioned = versionState(state, 2, metadata);

      expect(versioned.metadata?.migratedFrom).toBe(1);
      expect(versioned.metadata?.migratedAt).toBeDefined();
    });
  });

  describe('unversionState', () => {
    it('should unwrap versioned state', () => {
      const versioned: VersionedState = {
        version: 2,
        state: { count: 5 },
      };

      const result = unversionState(versioned);

      expect(result).not.toBeNull();
      expect(result?.version).toBe(2);
      expect(result?.state).toEqual({ count: 5 });
    });

    it('should handle unversioned state (legacy)', () => {
      const unversioned = { count: 5 };

      const result = unversionState(unversioned);

      expect(result).not.toBeNull();
      expect(result?.version).toBe(1); // Default version for legacy
      expect(result?.state).toEqual(unversioned);
    });

    it('should return null for invalid data', () => {
      const result1 = unversionState(null);
      expect(result1).toBeNull();
      
      const result2 = unversionState('invalid');
      expect(result2).toBeNull();
    });
  });

  describe('applyMigrations', () => {
    it('should return state as-is if version matches', () => {
      const versioned: VersionedState<TestStateV1> = {
        version: 2,
        state: { count: 5, name: 'test' },
      };

      const result = applyMigrations(versioned, {
        currentVersion: 2,
        storeName: 'TestStore',
      });

      expect(result).toEqual({ count: 5, name: 'test' });
    });

    it('should apply single migration', () => {
      const versioned: VersionedState<TestStateV1> = {
        version: 1,
        state: { count: 5, name: 'test' },
      };

      const migration = createMigration<TestStateV2>(
        2,
        (state) => {
          const v1 = state as TestStateV1;
          return {
            ...v1,
            metadata: {
              createdAt: new Date().toISOString(),
            },
          };
        },
        'Add metadata field',
      );

      const result = applyMigrations(versioned, {
        currentVersion: 2,
        storeName: 'TestStore',
        migrations: [migration],
      });

      expect(result.count).toBe(5);
      expect(result.name).toBe('test');
      expect((result as TestStateV2).metadata).toBeDefined();
      expect((result as TestStateV2).metadata.createdAt).toBeDefined();
    });

    it('should apply multiple migrations sequentially', () => {
      const versioned: VersionedState<TestStateV1> = {
        version: 1,
        state: { count: 5, name: 'test' },
      };

      const migrations = createMigrations<TestStateV3>(
        {
          toVersion: 2,
          migrate: (state) => {
            const v1 = state as TestStateV1;
            return {
              ...v1,
              metadata: {
                createdAt: new Date().toISOString(),
              },
            } as TestStateV2;
          },
          description: 'Add metadata.createdAt',
        },
        {
          toVersion: 3,
          migrate: (state) => {
            const v2 = state as TestStateV2;
            return {
              ...v2,
              metadata: {
                ...v2.metadata,
                updatedAt: new Date().toISOString(),
              },
            };
          },
          description: 'Add metadata.updatedAt',
        },
      );

      const result = applyMigrations(versioned, {
        currentVersion: 3,
        storeName: 'TestStore',
        migrations,
      });

      expect(result.count).toBe(5);
      expect(result.name).toBe('test');
      const v3 = result as TestStateV3;
      expect(v3.metadata.createdAt).toBeDefined();
      expect(v3.metadata.updatedAt).toBeDefined();
    });

    it('should use initial state if migration fails', () => {
      const versioned: VersionedState = {
        version: 1,
        state: { count: 5 },
      };

      const migration = createMigration(
        2,
        () => {
          throw new Error('Migration failed');
        },
      );

      const createInitialState = () => ({ count: 0 });

      const result = applyMigrations(versioned, {
        currentVersion: 2,
        storeName: 'TestStore',
        migrations: [migration],
        createInitialState,
      });

      expect(result).toEqual({ count: 0 });
    });

    it('should handle version newer than current', () => {
      const versioned: VersionedState = {
        version: 3,
        state: { count: 5 },
      };

      const createInitialState = () => ({ count: 0 });

      const result = applyMigrations(versioned, {
        currentVersion: 2,
        storeName: 'TestStore',
        createInitialState,
      });

      // Should reset to initial state when version is newer
      expect(result).toEqual({ count: 0 });
    });
  });

  describe('createVersionedStorage', () => {
    it('should store state with version', () => {
      // Ensure localStorage is available
      if (typeof window === 'undefined' || !window.localStorage) {
        return; // Skip test if localStorage not available
      }

      const storage = createVersionedStorage({
        currentVersion: 2,
        storeName: 'TestStore',
      });

      const state = { count: 5 };
      storage.setItem('test-key', JSON.stringify(state));

      // Verify it was stored
      const rawStored = localStorage.getItem('test-key');
      expect(rawStored).not.toBeNull();

      const stored = storage.getItem('test-key');
      expect(stored).not.toBeNull();

      const parsed = JSON.parse(stored!);
      expect(parsed.version).toBe(2);
      expect(parsed.state).toEqual(state);
    });

    it('should migrate state on getItem', () => {
      // Ensure localStorage is available
      if (typeof window === 'undefined' || !window.localStorage) {
        return; // Skip test if localStorage not available
      }

      // Store legacy state (version 1)
      localStorage.setItem('test-key', JSON.stringify({ count: 5 }));

      const migration = createMigration(
        2,
        (state) => {
          const v1 = state as { count: number };
          return {
            ...v1,
            name: 'migrated',
          };
        },
      );

      const storage = createVersionedStorage({
        currentVersion: 2,
        storeName: 'TestStore',
        migrations: [migration],
      });

      const stored = storage.getItem('test-key');
      expect(stored).not.toBeNull();

      const parsed = JSON.parse(stored!);
      expect(parsed.version).toBe(2);
      expect(parsed.state.count).toBe(5);
      expect(parsed.state.name).toBe('migrated');
      expect(parsed.metadata.migratedFrom).toBe(1);
    });

    it('should return null if item does not exist', () => {
      const storage = createVersionedStorage({
        currentVersion: 1,
        storeName: 'TestStore',
      });

      const result = storage.getItem('non-existent');
      expect(result).toBeNull();
    });

    it('should remove item', () => {
      localStorage.setItem('test-key', 'test-value');

      const storage = createVersionedStorage({
        currentVersion: 1,
        storeName: 'TestStore',
      });

      storage.removeItem('test-key');

      const item = localStorage.getItem('test-key');
      expect(item).toBeFalsy(); // null or undefined
    });

    it('should use initial state if migration fails', () => {
      // Ensure localStorage is available
      if (typeof window === 'undefined' || !window.localStorage) {
        return; // Skip test if localStorage not available
      }

      // Store versioned state
      localStorage.setItem(
        'test-key',
        JSON.stringify({ version: 1, state: { count: 5 } }),
      );

      const migration = createMigration(
        2,
        () => {
          throw new Error('Migration failed');
        },
      );

      const createInitialState = () => ({ count: 0 });

      const storage = createVersionedStorage({
        currentVersion: 2,
        storeName: 'TestStore',
        migrations: [migration],
        createInitialState,
      });

      const stored = storage.getItem('test-key');
      expect(stored).not.toBeNull();

      const parsed = JSON.parse(stored!);
      expect(parsed.version).toBe(2);
      expect(parsed.state).toEqual({ count: 0 });
    });
  });

  describe('createMigration', () => {
    it('should create migration with description', () => {
      const migration = createMigration(
        2,
        (state) => state,
        'Test migration',
      );

      expect(migration.toVersion).toBe(2);
      expect(migration.description).toBe('Test migration');
      expect(typeof migration.migrate).toBe('function');
    });
  });

  describe('createMigrations', () => {
    it('should create multiple migrations', () => {
      const migrations = createMigrations(
        {
          toVersion: 2,
          migrate: (state) => state,
          description: 'Migration 1',
        },
        {
          toVersion: 3,
          migrate: (state) => state,
          description: 'Migration 2',
        },
      );

      expect(migrations.length).toBe(2);
      expect(migrations[0].toVersion).toBe(2);
      expect(migrations[1].toVersion).toBe(3);
    });
  });
});

