/**
 * Sync Service
 * Main orchestration service for syncing Autotask data to PostgreSQL
 */

import postgresClient from './postgres-client';
import autotaskRateLimiter from './rate-limiter';
import { AutotaskClient } from './autotask-client';
import { createEntitySyncService, EntitySyncService } from './entity-sync';
import { 
  EntityType, 
  SyncType, 
  SyncStatus, 
  SyncConfig, 
  SyncResult, 
  SyncProgress,
  EntitySyncResult,
  SyncHistoryRecord 
} from '../types/sync';
import { 
  getEntitySyncOrder, 
  getAllEntitiesInOrder,
  generateSyncId,
  getEntityDisplayName 
} from '../utils/sync-helpers';
import { getLastSyncTime } from '../utils/db-helpers';
import { createSyncLogger, SyncPhase, categorizeError } from '../utils/sync-logger';

/**
 * Main Sync Service Class
 */
export class SyncService {
  private currentSyncId: string | null = null;
  private isSyncing = false;
  private autotaskClient: AutotaskClient;
  private entitySyncService: EntitySyncService;
  private logger = createSyncLogger({ component: 'SyncService' });

  constructor(autotaskClient: AutotaskClient) {
    this.autotaskClient = autotaskClient;
    this.entitySyncService = createEntitySyncService(autotaskClient);
  }

  /**
   * Start a full sync of all entities
   * @param triggeredBy User or system identifier
   * @param yearsBack Number of years to look back for time-based entities
   * @returns Sync result
   */
  async fullSync(triggeredBy?: string, yearsBack?: number): Promise<SyncResult> {
    const config: SyncConfig = {
      syncType: SyncType.FULL,
      entities: getAllEntitiesInOrder(),
      triggeredBy,
      yearsBack,
    };

    return await this.executeSync(config);
  }

  /**
   * Start an incremental sync of all entities
   * @param triggeredBy User or system identifier
   * @param yearsBack Number of years to look back for time-based entities
   * @returns Sync result
   */
  async incrementalSync(triggeredBy?: string, yearsBack?: number): Promise<SyncResult> {
    const config: SyncConfig = {
      syncType: SyncType.INCREMENTAL,
      entities: getAllEntitiesInOrder(),
      triggeredBy,
      yearsBack,
    };

    return await this.executeSync(config);
  }

  /**
   * Sync specific entities
   * @param entities Array of entity types to sync
   * @param syncType Type of sync (full or incremental)
   * @param triggeredBy User or system identifier
   * @param yearsBack Number of years to look back for time-based entities
   * @returns Sync result
   */
  async syncEntities(
    entities: EntityType[],
    syncType: SyncType = SyncType.ENTITY_SPECIFIC,
    triggeredBy?: string,
    yearsBack?: number
  ): Promise<SyncResult> {
    const config: SyncConfig = {
      syncType,
      entities: getEntitySyncOrder(entities),
      triggeredBy,
      yearsBack,
    };

    return await this.executeSync(config);
  }

  /**
   * Execute sync operation
   * @param config Sync configuration
   * @returns Sync result
   */
  private async executeSync(config: SyncConfig): Promise<SyncResult> {
    if (this.isSyncing) {
      throw new Error('A sync operation is already in progress');
    }

    this.isSyncing = true;
    const syncId = generateSyncId();
    this.currentSyncId = syncId;

    const startTime = new Date();
    const entityResults: EntitySyncResult[] = [];
    const errors: string[] = [];

    try {
      const syncLogger = this.logger.child({ syncId, syncType: config.syncType });
      syncLogger.info(`Starting ${config.syncType} sync`, {
        entities: config.entities.map(e => getEntityDisplayName(e)).join(', '),
        triggeredBy: config.triggeredBy,
        yearsBack: config.yearsBack,
      });

      // Sync each entity in order
      for (const entity of config.entities) {
        try {
          const entityLogger = syncLogger.child({ entityType: entity });
          const entityStartTime = entityLogger.start(`Syncing ${getEntityDisplayName(entity)}`);
          
          // Create sync history record
          const historyId = await this.createSyncHistory(
            entity,
            config.syncType,
            config.triggeredBy
          );

          let recordsAdded = 0;
          let recordsUpdated = 0;
          let recordsDeleted = 0;

          // Determine if incremental sync
          const isIncremental = config.syncType === SyncType.INCREMENTAL;
          const yearsBack = config.yearsBack || 2; // Default to 2 years

          // Execute entity sync
          const syncStats = await this.entitySyncService.syncEntity(entity, isIncremental, yearsBack);
          
          recordsAdded = syncStats.recordsAdded;
          recordsUpdated = syncStats.recordsUpdated;
          recordsDeleted = syncStats.recordsDeleted;
          
          const duration = Date.now() - entityStartTime;

          // Update sync history
          await this.updateSyncHistory(
            historyId,
            SyncStatus.COMPLETED,
            recordsAdded,
            recordsUpdated,
            recordsDeleted
          );

          entityResults.push({
            entityType: entity,
            success: true,
            recordsAdded,
            recordsUpdated,
            recordsDeleted,
            duration,
          });

          entityLogger.complete(`Syncing ${getEntityDisplayName(entity)}`, entityStartTime, {
            recordsAdded,
            recordsUpdated,
            recordsDeleted,
          });
        } catch (error) {
          const err = error instanceof Error ? error : new Error(String(error));
          const entityLogger = syncLogger.child({ entityType: entity });
          const entityName = getEntityDisplayName(entity);
          const errorCategory = categorizeError(err);
          
          // Log detailed error information
          entityLogger.error(`Failed to sync ${entityName}`, {
            errorCategory,
            syncType: config.syncType,
          }, err);
          
          const fullErrorMessage = `[${errorCategory}] ${err.message}`;
          errors.push(`${entityName}: ${fullErrorMessage}`);
          
          // Try to update sync history with error
          try {
            const historyId = await this.createSyncHistory(
              entity,
              config.syncType,
              config.triggeredBy
            );
            await this.updateSyncHistory(
              historyId,
              SyncStatus.FAILED,
              0,
              0,
              0,
              fullErrorMessage
            );
          } catch (historyError) {
            const histErr = historyError instanceof Error ? historyError : new Error(String(historyError));
            entityLogger.error('Failed to update sync history with error', {}, histErr);
          }

          entityResults.push({
            entityType: entity,
            success: false,
            recordsAdded: 0,
            recordsUpdated: 0,
            recordsDeleted: 0,
            duration: 0,
            error: fullErrorMessage,
          });
        }
      }

      // After all entities are synced, sync TicketTagAssociations (junction table)
      try {
        const tagAssocLogger = syncLogger.child({ entityType: 'ticket_tag_associations' });
        tagAssocLogger.info('Syncing TicketTagAssociations');
        const tagStats = await this.entitySyncService.syncTicketTagAssociations();
        tagAssocLogger.info('TicketTagAssociations sync complete', { recordsAdded: tagStats.recordsAdded });
      } catch (error) {
        const err = error instanceof Error ? error : new Error(String(error));
        syncLogger.warn('TicketTagAssociations sync failed, continuing', {}, err);
        errors.push(`TicketTagAssociations: ${err.message}`);
      }

      const endTime = new Date();
      const totalDuration = endTime.getTime() - startTime.getTime();

      const result: SyncResult = {
        syncId,
        syncType: config.syncType,
        status: errors.length === 0 ? SyncStatus.COMPLETED : SyncStatus.FAILED,
        entities: entityResults,
        totalRecordsAdded: entityResults.reduce((sum, r) => sum + r.recordsAdded, 0),
        totalRecordsUpdated: entityResults.reduce((sum, r) => sum + r.recordsUpdated, 0),
        totalRecordsDeleted: entityResults.reduce((sum, r) => sum + r.recordsDeleted, 0),
        startedAt: startTime,
        completedAt: endTime,
        duration: totalDuration,
        errors,
      };

      syncLogger.info(`Sync completed`, {
        duration: totalDuration,
        status: result.status,
        recordsAdded: result.totalRecordsAdded,
        recordsUpdated: result.totalRecordsUpdated,
        recordsDeleted: result.totalRecordsDeleted,
        errorCount: errors.length,
      });

      return result;
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      const syncLogger = this.logger.child({ syncId, syncType: config.syncType });
      
      syncLogger.error('Sync operation failed', {
        entitiesAttempted: config.entities.join(', '),
        successfulEntities: entityResults.filter(r => r.success).length,
        failedEntities: entityResults.filter(r => !r.success).length,
      }, err);
      
      throw error;
    } finally {
      this.isSyncing = false;
      this.currentSyncId = null;
    }
  }

  /**
   * Create sync history record
   * @param entity Entity type
   * @param syncType Sync type
   * @param triggeredBy User identifier
   * @returns Sync history ID
   */
  async createSyncHistory(
    entity: EntityType,
    syncType: SyncType,
    triggeredBy?: string
  ): Promise<number> {
    const query = `
      INSERT INTO sync_history (
        entity_type,
        sync_type,
        status,
        started_at,
        records_added,
        records_updated,
        records_deleted,
        triggered_by
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
      RETURNING id
    `;

    const result = await postgresClient.query<{ id: number }>(query, [
      entity,
      syncType,
      SyncStatus.STARTED,
      new Date(),
      0,
      0,
      0,
      triggeredBy || 'system',
    ]);

    return result.rows[0].id;
  }

  /**
   * Update sync history record
   * @param id Sync history ID
   * @param status Sync status
   * @param recordsAdded Number of records added
   * @param recordsUpdated Number of records updated
   * @param recordsDeleted Number of records deleted
   * @param errorMessage Optional error message
   */
  async updateSyncHistory(
    id: number,
    status: SyncStatus,
    recordsAdded: number,
    recordsUpdated: number,
    recordsDeleted: number,
    errorMessage?: string
  ): Promise<void> {
    const query = `
      UPDATE sync_history
      SET status = $1,
          completed_at = $2,
          records_added = $3,
          records_updated = $4,
          records_deleted = $5,
          error_message = $6
      WHERE id = $7
    `;

    await postgresClient.query(query, [
      status,
      new Date(),
      recordsAdded,
      recordsUpdated,
      recordsDeleted,
      errorMessage || null,
      id,
    ]);
  }

  /**
   * Get sync history
   * @param limit Number of records to return
   * @param entityType Optional entity type filter
   * @param offset Offset for pagination
   * @returns Array of sync history records
   */
  async getSyncHistory(
    limit: number = 50,
    entityType?: EntityType,
    offset: number = 0
  ): Promise<SyncHistoryRecord[]> {
    let query = `
      SELECT *
      FROM sync_history
    `;

    const params: any[] = [];
    
    if (entityType) {
      query += ` WHERE entity_type = $1`;
      params.push(entityType);
    }

    query += ` ORDER BY started_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
    params.push(limit, offset);

    const result = await postgresClient.query<SyncHistoryRecord>(query, params);
    return result.rows;
  }

  /**
   * Get last sync info for all entities
   * @returns Map of entity type to last sync record
   */
  async getLastSyncInfo(): Promise<Map<EntityType, SyncHistoryRecord>> {
    const query = `
      SELECT DISTINCT ON (entity_type) *
      FROM sync_history
      WHERE status = 'completed'
      ORDER BY entity_type, completed_at DESC
    `;

    const result = await postgresClient.query<SyncHistoryRecord>(query);
    const map = new Map<EntityType, SyncHistoryRecord>();

    for (const row of result.rows) {
      // Check if the entity_type value exists in the EntityType enum values
      const entityTypeValues = Object.values(EntityType);
      if (entityTypeValues.includes(row.entity_type as EntityType)) {
        map.set(row.entity_type as EntityType, row);
      }
    }

    return map;
  }

  /**
   * Check if sync is currently running
   * @returns True if sync is in progress
   */
  isSyncInProgress(): boolean {
    return this.isSyncing;
  }

  /**
   * Get current sync ID
   * @returns Current sync ID or null
   */
  getCurrentSyncId(): string | null {
    return this.currentSyncId;
  }

  /**
   * Cancel current sync operation
   */
  async cancelSync(): Promise<void> {
    if (!this.isSyncing) {
      throw new Error('No sync operation in progress');
    }

    // TODO: Implement graceful cancellation
    this.isSyncing = false;
    const cancelledSyncId = this.currentSyncId || 'unknown';
    this.currentSyncId = null;
    
    this.logger.warn('Sync operation cancelled', { syncId: cancelledSyncId });
  }
}

/**
 * Create sync service instance
 * @param autotaskClient Autotask client instance
 * @returns SyncService instance
 */
export function createSyncService(autotaskClient: AutotaskClient): SyncService {
  return new SyncService(autotaskClient);
}
