import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import { CredentialsService } from '../credentials/credentials.service';
import { IntegrationType, SyncStatus, Prisma } from '@prisma/client';

/**
 * Sync history item for API responses
 */
export interface SyncHistoryItem {
  id: string;
  status: SyncStatus;
  startedAt: Date;
  completedAt: Date | null;
  duration: number | null;
  recordsProcessed: number | null;
  error: string | null;
  metadata: Record<string, unknown> | null;
}

/**
 * Sync status response
 */
export interface SyncStatusResponse {
  credentialId: string;
  type: IntegrationType;
  name: string;
  isActive: boolean;
  currentStatus: SyncStatus;
  lastSync: Date | null;
  lastError: string | null;
  isCurrentlyRunning: boolean;
}

/**
 * Paginated sync history response
 */
export interface PaginatedSyncHistory {
  data: SyncHistoryItem[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

@Injectable()
export class SyncService {
  private readonly logger = new Logger(SyncService.name);
  private readonly runningJobs = new Map<string, boolean>();

  constructor(
    private readonly prisma: PrismaService,
    private readonly credentialsService: CredentialsService,
  ) {}

  /**
   * Initiates a manual sync for an integration credential
   */
  async triggerSync(
    credentialId: string,
    fullSync: boolean = false,
  ): Promise<{ jobId: string; message: string }> {
    const credential = await this.prisma.integrationCredential.findUnique({
      where: { id: credentialId },
    });

    if (!credential) {
      throw new NotFoundException(`Credential ${credentialId} not found`);
    }

    if (!credential.isActive) {
      throw new BadRequestException(`Credential ${credentialId} is deactivated`);
    }

    // Check if a sync is already running
    if (this.runningJobs.get(credentialId)) {
      throw new BadRequestException(
        `A sync job is already running for credential ${credentialId}`,
      );
    }

    // Create sync history entry
    const syncHistory = await this.prisma.integrationSyncHistory.create({
      data: {
        credentialId,
        status: SyncStatus.PENDING,
        metadata: { fullSync },
      },
    });

    // Update credential status
    await this.credentialsService.updateSyncStatus(
      credentialId,
      SyncStatus.PENDING,
      null,
    );

    // Start sync in background (in production, this would be handled by BullMQ)
    this.executeSyncJob(credentialId, syncHistory.id, fullSync);

    this.logger.log(
      `Triggered ${fullSync ? 'full' : 'incremental'} sync for credential ${credentialId}`,
    );

    return {
      jobId: syncHistory.id,
      message: `Sync job ${syncHistory.id} started for ${credential.type}:${credential.name}`,
    };
  }

  /**
   * Gets the current sync status for a credential
   */
  async getSyncStatus(credentialId: string): Promise<SyncStatusResponse> {
    const credential = await this.prisma.integrationCredential.findUnique({
      where: { id: credentialId },
    });

    if (!credential) {
      throw new NotFoundException(`Credential ${credentialId} not found`);
    }

    return {
      credentialId: credential.id,
      type: credential.type,
      name: credential.name,
      isActive: credential.isActive,
      currentStatus: credential.syncStatus,
      lastSync: credential.lastSync,
      lastError: credential.syncError,
      isCurrentlyRunning: this.runningJobs.get(credentialId) || false,
    };
  }

  /**
   * Gets the sync history for a credential
   */
  async getSyncHistory(
    credentialId: string,
    page: number = 1,
    limit: number = 20,
  ): Promise<PaginatedSyncHistory> {
    const credential = await this.prisma.integrationCredential.findUnique({
      where: { id: credentialId },
    });

    if (!credential) {
      throw new NotFoundException(`Credential ${credentialId} not found`);
    }

    const [history, total] = await Promise.all([
      this.prisma.integrationSyncHistory.findMany({
        where: { credentialId },
        orderBy: { startedAt: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.integrationSyncHistory.count({
        where: { credentialId },
      }),
    ]);

    return {
      data: history.map((h) => ({
        id: h.id,
        status: h.status,
        startedAt: h.startedAt,
        completedAt: h.completedAt,
        duration: h.duration,
        recordsProcessed: h.recordsProcessed,
        error: h.error,
        metadata: h.metadata as Record<string, unknown> | null,
      })),
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Gets a specific sync job status
   */
  async getSyncJobStatus(jobId: string): Promise<SyncHistoryItem> {
    const job = await this.prisma.integrationSyncHistory.findUnique({
      where: { id: jobId },
    });

    if (!job) {
      throw new NotFoundException(`Sync job ${jobId} not found`);
    }

    return {
      id: job.id,
      status: job.status,
      startedAt: job.startedAt,
      completedAt: job.completedAt,
      duration: job.duration,
      recordsProcessed: job.recordsProcessed,
      error: job.error,
      metadata: job.metadata as Record<string, unknown> | null,
    };
  }

  /**
   * Executes the sync job (simplified in-process version)
   * In production, this would be handled by BullMQ processor
   */
  private async executeSyncJob(
    credentialId: string,
    historyId: string,
    fullSync: boolean,
  ): Promise<void> {
    this.runningJobs.set(credentialId, true);
    const startTime = Date.now();

    try {
      // Update status to running
      await this.updateSyncHistory(historyId, {
        status: SyncStatus.RUNNING,
      });
      await this.credentialsService.updateSyncStatus(
        credentialId,
        SyncStatus.RUNNING,
        null,
      );

      // Get credential details
      const { type, credentials } =
        await this.credentialsService.getCredentialsForSync(credentialId);

      // Execute integration-specific sync
      const result = await this.performSync(type, credentials, fullSync);

      const duration = Date.now() - startTime;

      // Update history with success
      await this.updateSyncHistory(historyId, {
        status: SyncStatus.SUCCESS,
        completedAt: new Date(),
        duration,
        recordsProcessed: result.recordsProcessed,
        metadata: result.metadata,
      });

      // Update credential status
      await this.credentialsService.updateSyncStatus(
        credentialId,
        SyncStatus.SUCCESS,
        null,
      );

      this.logger.log(
        `Sync completed for credential ${credentialId}: ${result.recordsProcessed} records in ${duration}ms`,
      );
    } catch (error) {
      const duration = Date.now() - startTime;
      const errorMessage =
        error instanceof Error ? error.message : 'Unknown sync error';

      // Update history with error
      await this.updateSyncHistory(historyId, {
        status: SyncStatus.ERROR,
        completedAt: new Date(),
        duration,
        error: errorMessage,
      });

      // Update credential status
      await this.credentialsService.updateSyncStatus(
        credentialId,
        SyncStatus.ERROR,
        errorMessage,
      );

      this.logger.error(
        `Sync failed for credential ${credentialId}: ${errorMessage}`,
      );
    } finally {
      this.runningJobs.delete(credentialId);
    }
  }

  /**
   * Updates a sync history entry
   */
  private async updateSyncHistory(
    historyId: string,
    data: Partial<{
      status: SyncStatus;
      completedAt: Date;
      duration: number;
      recordsProcessed: number;
      error: string;
      metadata: Record<string, unknown>;
    }>,
  ): Promise<void> {
    await this.prisma.integrationSyncHistory.update({
      where: { id: historyId },
      data: data as any,
    });
  }

  /**
   * Performs integration-specific sync operations
   */
  private async performSync(
    type: IntegrationType,
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // Simulate sync delay
    await this.delay(1000 + Math.random() * 2000);

    // Each integration type would have its own sync logic
    switch (type) {
      case IntegrationType.PLENTYONE:
        return this.syncPlentyOne(credentials, fullSync);
      case IntegrationType.ZULIP:
        return this.syncZulip(credentials, fullSync);
      case IntegrationType.TODOIST:
        return this.syncTodoist(credentials, fullSync);
      case IntegrationType.FREESCOUT:
        return this.syncFreescout(credentials, fullSync);
      case IntegrationType.NEXTCLOUD:
        return this.syncNextcloud(credentials, fullSync);
      case IntegrationType.ECODMS:
        return this.syncEcodms(credentials, fullSync);
      case IntegrationType.GEMBADOCS:
        return this.syncGembadocs(credentials, fullSync);
      default:
        return { recordsProcessed: 0, metadata: { message: 'No sync implemented' } };
    }
  }

  // Integration-specific sync methods (placeholder implementations)
  private async syncPlentyOne(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: call PlentyOne API to sync orders, products, etc.
    return {
      recordsProcessed: Math.floor(Math.random() * 100),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'orders' },
    };
  }

  private async syncZulip(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync Zulip messages, channels
    return {
      recordsProcessed: Math.floor(Math.random() * 50),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'messages' },
    };
  }

  private async syncTodoist(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync Todoist tasks, projects
    return {
      recordsProcessed: Math.floor(Math.random() * 30),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'tasks' },
    };
  }

  private async syncFreescout(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync FreeScout tickets, conversations
    return {
      recordsProcessed: Math.floor(Math.random() * 80),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'tickets' },
    };
  }

  private async syncNextcloud(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync Nextcloud files, calendars
    return {
      recordsProcessed: Math.floor(Math.random() * 200),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'files' },
    };
  }

  private async syncEcodms(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync ecoDMS documents
    return {
      recordsProcessed: Math.floor(Math.random() * 150),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'documents' },
    };
  }

  private async syncGembadocs(
    credentials: Record<string, unknown>,
    fullSync: boolean,
  ): Promise<{ recordsProcessed: number; metadata?: Record<string, unknown> }> {
    // In production: sync GembaDocs documents, audits
    return {
      recordsProcessed: Math.floor(Math.random() * 40),
      metadata: { syncType: fullSync ? 'full' : 'incremental', entity: 'audits' },
    };
  }

  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
