import { ISync, EntityType, SyncStatus, IEventBus, IEntitySavedPayload, IEntityDeletedPayload } from '../types/CalendarTypes';
import { IEntityService } from './IEntityService';
import { SyncPlugin } from './SyncPlugin';
import { IndexedDBContext } from './IndexedDBContext';
import { CoreEvents } from '../constants/CoreEvents';
import { diff } from 'json-diff-ts';

/**
 * BaseEntityService<T extends ISync> - Abstract base class for all entity services
 *
 * PROVIDES:
 * - Generic CRUD operations (get, getAll, save, delete)
 * - Sync status management (delegates to SyncPlugin)
 * - Serialization hooks (override in subclass if needed)
 */
export abstract class BaseEntityService<T extends ISync> implements IEntityService<T> {
  abstract readonly storeName: string;
  abstract readonly entityType: EntityType;

  private syncPlugin: SyncPlugin<T>;
  private context: IndexedDBContext;
  protected eventBus: IEventBus;

  constructor(context: IndexedDBContext, eventBus: IEventBus) {
    this.context = context;
    this.eventBus = eventBus;
    this.syncPlugin = new SyncPlugin<T>(this);
  }

  protected get db(): IDBDatabase {
    return this.context.getDatabase();
  }

  /**
   * Serialize entity before storing in IndexedDB
   */
  protected serialize(entity: T): unknown {
    return entity;
  }

  /**
   * Deserialize data from IndexedDB back to entity
   */
  protected deserialize(data: unknown): T {
    return data as T;
  }

  /**
   * Get a single entity by ID
   */
  async get(id: string): Promise<T | null> {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get(id);

      request.onsuccess = () => {
        const data = request.result;
        resolve(data ? this.deserialize(data) : null);
      };

      request.onerror = () => {
        reject(new Error(`Failed to get ${this.entityType} ${id}: ${request.error}`));
      };
    });
  }

  /**
   * Get all entities
   */
  async getAll(): Promise<T[]> {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.getAll();

      request.onsuccess = () => {
        const data = request.result as unknown[];
        const entities = data.map(item => this.deserialize(item));
        resolve(entities);
      };

      request.onerror = () => {
        reject(new Error(`Failed to get all ${this.entityType}s: ${request.error}`));
      };
    });
  }

  /**
   * Save an entity (create or update)
   * Emits ENTITY_SAVED event with operation type and changes (diff for updates)
   * @param entity - Entity to save
   * @param silent - If true, skip event emission (used for seeding)
   */
  async save(entity: T, silent = false): Promise<void> {
    const entityId = (entity as unknown as { id: string }).id;
    const existingEntity = await this.get(entityId);
    const isCreate = existingEntity === null;

    // Calculate changes: full entity for create, diff for update
    let changes: unknown;
    if (isCreate) {
      changes = entity;
    } else {
      const existingSerialized = this.serialize(existingEntity);
      const newSerialized = this.serialize(entity);
      changes = diff(existingSerialized, newSerialized);
    }

    const serialized = this.serialize(entity);

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.put(serialized);

      request.onsuccess = () => {
        // Only emit event if not silent (silent used for seeding)
        if (!silent) {
          const payload: IEntitySavedPayload = {
            entityType: this.entityType,
            entityId,
            operation: isCreate ? 'create' : 'update',
            changes,
            timestamp: Date.now()
          };
          this.eventBus.emit(CoreEvents.ENTITY_SAVED, payload);
        }
        resolve();
      };

      request.onerror = () => {
        reject(new Error(`Failed to save ${this.entityType} ${entityId}: ${request.error}`));
      };
    });
  }

  /**
   * Delete an entity
   * Emits ENTITY_DELETED event
   */
  async delete(id: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.delete(id);

      request.onsuccess = () => {
        const payload: IEntityDeletedPayload = {
          entityType: this.entityType,
          entityId: id,
          operation: 'delete',
          timestamp: Date.now()
        };
        this.eventBus.emit(CoreEvents.ENTITY_DELETED, payload);
        resolve();
      };

      request.onerror = () => {
        reject(new Error(`Failed to delete ${this.entityType} ${id}: ${request.error}`));
      };
    });
  }

  // Sync methods - delegate to SyncPlugin
  async markAsSynced(id: string): Promise<void> {
    return this.syncPlugin.markAsSynced(id);
  }

  async markAsError(id: string): Promise<void> {
    return this.syncPlugin.markAsError(id);
  }

  async getSyncStatus(id: string): Promise<SyncStatus | null> {
    return this.syncPlugin.getSyncStatus(id);
  }

  async getBySyncStatus(syncStatus: string): Promise<T[]> {
    return this.syncPlugin.getBySyncStatus(syncStatus);
  }
}
