/**
 * State Versioning
 * FE-STATE-011: Add state versioning for migration support
 * 
 * Provides versioning and migration support for Zustand persisted state.
 * Allows automatic migration of state between application versions.
 */

import { logger } from './logger';

/**
 * State version number
 */
export type StateVersion = number;

/**
 * Migration function that transforms state from one version to another
 */
export type MigrationFunction<T = unknown> = (state: unknown) => T;

/**
 * Migration definition
 */
export interface Migration<T = unknown> {
  /** Target version after migration */
  toVersion: StateVersion;
  /** Migration function */
  migrate: MigrationFunction<T>;
  /** Optional description */
  description?: string;
}

/**
 * Versioned state structure
 */
export interface VersionedState<T = unknown> {
  /** State version */
  version: StateVersion;
  /** Actual state data */
  state: T;
  /** Optional metadata */
  metadata?: {
    migratedAt?: string;
    migratedFrom?: StateVersion;
  };
}

/**
 * Versioning configuration
 */
export interface VersioningConfig<T = unknown> {
  /** Current version of the state */
  currentVersion: StateVersion;
  /** Store name for logging */
  storeName: string;
  /** Migrations to apply */
  migrations?: Migration<T>[];
  /** Function to create initial state if migration fails */
  createInitialState?: () => T;
}

/**
 * Apply migrations to state
 */
export function applyMigrations<T = unknown>(
  versionedState: VersionedState<T>,
  config: VersioningConfig<T>,
): T {
  const { currentVersion, storeName, migrations = [], createInitialState } = config;
  let { version, state } = versionedState;

  // If already at current version, return state as-is
  if (version === currentVersion) {
    return state as T;
  }

  // If version is newer than current, log warning
  if (version > currentVersion) {
    logger.warn(
      `[StateVersioning:${storeName}] State version ${version} is newer than current ${currentVersion}. This may indicate a downgrade.`,
    );
    // Optionally reset to initial state or return as-is
    if (createInitialState) {
      logger.warn(`[StateVersioning:${storeName}] Resetting to initial state due to version mismatch.`);
      return createInitialState();
    }
    return state as T;
  }

  // Sort migrations by target version
  const sortedMigrations = [...migrations].sort((a, b) => a.toVersion - b.toVersion);

  // Apply migrations sequentially
  let currentState = state;
  let lastVersion = version;

  for (const migration of sortedMigrations) {
    // Skip migrations that are not needed
    if (migration.toVersion <= version) {
      continue;
    }

    // Skip migrations beyond current version
    if (migration.toVersion > currentVersion) {
      continue;
    }

    try {
      logger.info(
        `[StateVersioning:${storeName}] Migrating from version ${lastVersion} to ${migration.toVersion}${migration.description ? `: ${migration.description}` : ''}`,
      );

      currentState = migration.migrate(currentState);
      lastVersion = migration.toVersion;

      logger.debug(`[StateVersioning:${storeName}] Migration to version ${migration.toVersion} completed.`);
    } catch (error) {
      logger.error(
        `[StateVersioning:${storeName}] Migration to version ${migration.toVersion} failed:`,
        error,
      );

      // If migration fails and we have initial state, use it
      if (createInitialState) {
        logger.warn(`[StateVersioning:${storeName}] Using initial state due to migration failure.`);
        return createInitialState();
      }

      // Otherwise, throw the error
      throw new Error(
        `Migration to version ${migration.toVersion} failed: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  // If we didn't reach current version, log warning
  if (lastVersion < currentVersion) {
    logger.warn(
      `[StateVersioning:${storeName}] State migrated to version ${lastVersion} but current version is ${currentVersion}. Some migrations may be missing.`,
    );
  }

  return currentState as T;
}

/**
 * Wrap state with version information
 */
export function versionState<T = unknown>(
  state: T,
  version: StateVersion,
  metadata?: VersionedState<T>['metadata'],
): VersionedState<T> {
  return {
    version,
    state,
    metadata: {
      ...metadata,
      migratedAt: metadata?.migratedAt || new Date().toISOString(),
    },
  };
}

/**
 * Unwrap versioned state (extract state and version)
 */
export function unversionState<T = unknown>(
  data: unknown,
): { state: T; version: StateVersion } | null {
  // Check if data is already versioned
  if (
    typeof data === 'object' &&
    data !== null &&
    'version' in data &&
    'state' in data
  ) {
    const versioned = data as VersionedState<T>;
    return {
      state: versioned.state as T,
      version: versioned.version,
    };
  }

  // If not versioned but is a valid object, assume version 1 (legacy state)
  if (typeof data === 'object' && data !== null) {
    return {
      state: data as T,
      version: 1,
    };
  }

  // Invalid data
  return null;
}

/**
 * Create versioned storage adapter for Zustand persist middleware
 */
export function createVersionedStorage<T = unknown>(
  config: VersioningConfig<T>,
): {
  getItem: (name: string) => string | null;
  setItem: (name: string, value: string) => void;
  removeItem: (name: string) => void;
} {
  const { currentVersion, storeName, migrations, createInitialState } = config;

  return {
    getItem: (name: string): string | null => {
      try {
        if (typeof window === 'undefined') {
          return null;
        }

        const stored = localStorage.getItem(name);
        if (!stored) {
          return null;
        }

        const parsed = JSON.parse(stored);
        const unversioned = unversionState<T>(parsed);

        if (!unversioned) {
          logger.warn(`[StateVersioning:${storeName}] Invalid stored state format.`);
          if (createInitialState) {
            const initialState = createInitialState();
            return JSON.stringify(versionState(initialState, currentVersion));
          }
          return null;
        }

        const { state, version } = unversioned;

        // If version matches, return as-is
        if (version === currentVersion) {
          return stored;
        }

        // Apply migrations
        try {
          const migratedState = applyMigrations(
            { version, state },
            config,
          );

          // Save migrated state
          const versioned = versionState(migratedState, currentVersion, {
            migratedFrom: version,
          });
          localStorage.setItem(name, JSON.stringify(versioned));

          logger.info(
            `[StateVersioning:${storeName}] State migrated from version ${version} to ${currentVersion}.`,
          );

          return JSON.stringify(versioned);
        } catch (error) {
          logger.error(
            `[StateVersioning:${storeName}] Migration failed:`,
            error,
          );

          if (createInitialState) {
            try {
              const initialState = createInitialState();
              const versioned = versionState(initialState, currentVersion);
              localStorage.setItem(name, JSON.stringify(versioned));
              return JSON.stringify(versioned);
            } catch (initError) {
              logger.error(
                `[StateVersioning:${storeName}] Failed to create initial state:`,
                initError,
              );
              return null;
            }
          }

          // Return null if migration fails and no initial state
          return null;
        }
      } catch (error) {
        logger.error(`[StateVersioning:${storeName}] Failed to get item:`, error);
        return null;
      }
    },

    setItem: (name: string, value: string): void => {
      try {
        if (typeof window === 'undefined') {
          return;
        }

        // Parse and version the state before storing
        let parsed: unknown;
        try {
          parsed = JSON.parse(value);
        } catch {
          // If parsing fails, treat as plain string or use value as-is
          parsed = value;
        }

        // Check if already versioned
        const unversioned = unversionState(parsed);
        if (unversioned && unversioned.version === currentVersion) {
          // Already versioned and at current version, store as-is
          const versioned = versionState(unversioned.state, currentVersion);
          localStorage.setItem(name, JSON.stringify(versioned));
        } else if (unversioned) {
          // Versioned but different version, migrate first
          const migratedState = applyMigrations(
            { version: unversioned.version, state: unversioned.state },
            config,
          );
          const versioned = versionState(migratedState, currentVersion);
          localStorage.setItem(name, JSON.stringify(versioned));
        } else {
          // Not versioned, version it
          const versioned = versionState(parsed, currentVersion);
          localStorage.setItem(name, JSON.stringify(versioned));
        }
      } catch (error) {
        logger.error(`[StateVersioning:${storeName}] Failed to set item:`, error);
      }
    },

    removeItem: (name: string): void => {
      try {
        if (typeof window === 'undefined') {
          return;
        }
        localStorage.removeItem(name);
      } catch (error) {
        logger.error(`[StateVersioning:${storeName}] Failed to remove item:`, error);
      }
    },
  };
}

/**
 * Helper to create a migration
 */
export function createMigration<T = unknown>(
  toVersion: StateVersion,
  migrate: MigrationFunction<T>,
  description?: string,
): Migration<T> {
  return {
    toVersion,
    migrate,
    description,
  };
}

/**
 * Helper to create multiple migrations at once
 */
export function createMigrations<T = unknown>(
  ...migrations: Array<{
    toVersion: StateVersion;
    migrate: MigrationFunction<T>;
    description?: string;
  }>
): Migration<T>[] {
  return migrations.map((m) => createMigration(m.toVersion, m.migrate, m.description));
}

