import { Type, effect, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
  getState,
  patchState,
  signalStoreFeature,
  withHooks,
  withMethods,
} from '@ngrx/signals';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { z } from 'zod';
import { logger } from '@isa/core/logging';
import { StorageProvider } from './storage-providers';
import { injectStorage } from './storage';

export interface WithStorageConfig<T = object> {
  autosave?: boolean; // default: false
  debounceTime?: number; // default: 300
  autoload?: boolean; // default: true
  validator?: (data: unknown) => data is T; // Custom validation function
  schema?: z.ZodType<T>; // Zod schema for validation
  fallbackState?: Partial<T>; // Fallback state when validation fails
  excludeProperties?: string[]; // Properties to exclude from storage
}

export function withStorage<T extends object>(
  storageKey: string,
  storageProvider: Type<StorageProvider>,
  config: WithStorageConfig<T> = {},
) {
  // Input validation
  if (
    !storageKey ||
    typeof storageKey !== 'string' ||
    storageKey.trim() === ''
  ) {
    throw new Error(`Invalid storage key: ${storageKey}`);
  }

  if (!storageProvider) {
    throw new Error('Storage provider is required');
  }

  const {
    autosave = false,
    debounceTime: debounceTimeMs = 300,
    autoload = true,
    validator,
    schema,
    fallbackState,
    excludeProperties = [],
  } = config;

  // Validate configuration
  if (debounceTimeMs < 0) {
    throw new Error('Debounce time must be non-negative');
  }

  return signalStoreFeature(
    withMethods(
      (
        store,
        storage = injectStorage(storageProvider),
        log = logger(() => ({
          module: 'storage',
          storageKey,
          autosave,
          autoload,
          debounceTime: debounceTimeMs,
        })),
      ) => ({
        saveToStorage: () => {
          try {
            const state = getState(store);

            // Filter out excluded properties if specified
            const filteredState =
              excludeProperties.length > 0
                ? Object.fromEntries(
                    Object.entries(state).filter(
                      ([key]) => !excludeProperties.includes(key),
                    ),
                  )
                : state;

            storage.set(storageKey, filteredState);
            log.debug('Successfully saved state');
          } catch (error) {
            log.error(
              'Failed to save state',
              error instanceof Error ? error : undefined,
            );
            throw new Error(
              `Storage save failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
            );
          }
        },
        loadFromStorage: () => {
          try {
            const data = storage.get(storageKey);

            if (!data) {
              log.debug('No data found in storage');
              if (fallbackState && Object.keys(fallbackState).length > 0) {
                patchState(store, fallbackState);
                log.debug('Applied fallback state');
              }
              return;
            }

            // Enhanced validation
            if (
              typeof data !== 'object' ||
              data === null ||
              Array.isArray(data)
            ) {
              log.warn('Invalid data type in storage', () => ({
                dataType: typeof data,
              }));
              if (fallbackState && Object.keys(fallbackState).length > 0) {
                patchState(store, fallbackState);
                log.debug('Applied fallback state due to invalid data type');
              }
              return;
            }

            // Zod schema validation
            if (schema) {
              try {
                const validatedData = schema.parse(data);
                patchState(store, validatedData);
                log.debug('Successfully loaded and validated state');
                return;
              } catch (validationError) {
                log.warn('Schema validation failed', () => ({
                  validationError,
                }));
                if (fallbackState && Object.keys(fallbackState).length > 0) {
                  patchState(store, fallbackState);
                  log.debug(
                    'Applied fallback state due to schema validation failure',
                  );
                }
                return;
              }
            }

            // Custom validator function
            if (validator && !validator(data)) {
              log.warn('Custom validation failed');
              if (fallbackState && Object.keys(fallbackState).length > 0) {
                patchState(store, fallbackState);
                log.debug(
                  'Applied fallback state due to custom validation failure',
                );
              }
              return;
            }

            // Basic validation passed - apply state
            patchState(store, data as Partial<T>);
            log.debug('Successfully loaded state');
          } catch (error) {
            log.error(
              'Failed to load state',
              error instanceof Error ? error : undefined,
            );
            if (fallbackState && Object.keys(fallbackState).length > 0) {
              try {
                patchState(store, fallbackState);
                log.debug('Applied fallback state due to load error');
              } catch (fallbackError) {
                log.error(
                  'Failed to apply fallback state',
                  fallbackError instanceof Error ? fallbackError : undefined,
                );
              }
            }
            // Don't throw here as we want the app to continue working even if load fails
          }
        },
      }),
    ),
    withHooks(
      (
        store,
        log = logger(() => ({
          module: 'storage',
          storageKey,
          autosave,
          autoload,
          debounceTime: debounceTimeMs,
        })),
      ) => {
        let cleanup: (() => void) | null = null;

        return {
          onInit() {
            // Load initial state if autoload is enabled
            if (autoload) {
              try {
                store.loadFromStorage();
              } catch (error) {
                log.error(
                  'Failed to load initial state',
                  error instanceof Error ? error : undefined,
                );
              }
            }

            if (autosave) {
              const destroyRef = inject(DestroyRef);
              const saveSubject = new Subject<void>();

              const subscription = saveSubject
                .pipe(
                  debounceTime(debounceTimeMs),
                  takeUntilDestroyed(destroyRef),
                )
                .subscribe(() => {
                  try {
                    store.saveToStorage();
                  } catch (error) {
                    log.error(
                      'Autosave failed',
                      error instanceof Error ? error : undefined,
                    );
                    // Don't rethrow - keep autosave running
                  }
                });

              const effectRef = effect(() => {
                try {
                  getState(store);
                  saveSubject.next();
                } catch (error) {
                  log.error(
                    'Effect error in autosave',
                    error instanceof Error ? error : undefined,
                  );
                  // Don't rethrow - keep effect running
                }
              });

              // Set up comprehensive cleanup
              cleanup = () => {
                if (!subscription.closed) {
                  subscription.unsubscribe();
                }
                if (!saveSubject.closed) {
                  saveSubject.complete();
                }
                effectRef.destroy();
              };

              // Register cleanup with DestroyRef
              destroyRef.onDestroy(() => {
                cleanup?.();
              });
            }
          },

          onDestroy() {
            // Additional cleanup hook
            cleanup?.();
          },
        };
      },
    ),
  );
}
