// ============================================================
// Lexware Office - Sync Jobs (Cron)
// ============================================================

import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { RedisService } from '../redis/redis.service';
import { LexwareClientService } from './lexware-client.service';
import { LexwareContactsService } from './lexware-contacts.service';
import { LexwareVouchersService } from './lexware-vouchers.service';

@Injectable()
export class LexwareSyncService {
  private readonly logger = new Logger(LexwareSyncService.name);

  constructor(
    private readonly prisma: CrmPrismaService,
    private readonly redis: RedisService,
    private readonly lexwareClient: LexwareClientService,
    private readonly contactsService: LexwareContactsService,
    private readonly vouchersService: LexwareVouchersService,
  ) {}

  // --------------------------------------------------------
  // Job 1: Beleg-Cache aktualisieren (alle 4 Stunden)
  // --------------------------------------------------------

  @Cron(CronExpression.EVERY_4_HOURS)
  async syncVouchers(): Promise<void> {
    if (!this.lexwareClient.isEnabled()) return;

    // Distributed Lock: Nur eine Instanz gleichzeitig
    const lockKey = 'lexware:sync:vouchers:lock';
    const acquired = await this.acquireLock(lockKey, 1800); // 30 Min Lock
    if (!acquired) {
      this.logger.log('Beleg-Sync: Lock nicht erhalten. Ueberspringe.');
      return;
    }

    try {
      this.logger.log('Starte Beleg-Sync fuer alle verknuepften Entitaeten...');

      // Alle Companies mit Lexware-Verknuepfung
      const companies = await this.prisma.company.findMany({
        where: { lexwareContactId: { not: null } },
        select: {
          id: true,
          tenantId: true,
          lexwareContactId: true,
          name: true,
        },
      });

      for (const company of companies) {
        try {
          await this.vouchersService.fetchAndCacheVouchers(
            company.tenantId,
            company.lexwareContactId!,
            company.id,
            null,
          );
        } catch (error) {
          this.logger.warn(
            `Beleg-Sync fehlgeschlagen fuer Company "${company.name}": ${error instanceof Error ? error.message : 'Unbekannt'}`,
          );
        }
      }

      // Alle Contacts mit Lexware-Verknuepfung
      const contacts = await this.prisma.contact.findMany({
        where: { lexwareContactId: { not: null } },
        select: {
          id: true,
          tenantId: true,
          lexwareContactId: true,
          firstName: true,
          lastName: true,
        },
      });

      for (const contact of contacts) {
        try {
          await this.vouchersService.fetchAndCacheVouchers(
            contact.tenantId,
            contact.lexwareContactId!,
            null,
            contact.id,
          );
        } catch (error) {
          this.logger.warn(
            `Beleg-Sync fehlgeschlagen fuer Contact "${contact.firstName} ${contact.lastName}": ${error instanceof Error ? error.message : 'Unbekannt'}`,
          );
        }
      }

      this.logger.log(
        `Beleg-Sync abgeschlossen. ${companies.length} Companies, ${contacts.length} Contacts verarbeitet.`,
      );
    } finally {
      await this.releaseLock(lockKey);
    }
  }

  // --------------------------------------------------------
  // Job 2: ERP-Tag Push (alle 30 Minuten)
  // --------------------------------------------------------

  @Cron('0 */30 * * * *')
  async pushErpTaggedEntities(): Promise<void> {
    if (!this.lexwareClient.isEnabled()) return;

    const lockKey = 'lexware:sync:push:lock';
    const acquired = await this.acquireLock(lockKey, 900); // 15 Min Lock
    if (!acquired) {
      this.logger.log('ERP-Push: Lock nicht erhalten. Ueberspringe.');
      return;
    }

    try {
      this.logger.log('Starte ERP-Push fuer getaggte Entitaeten...');

      // Companies mit "ERP" Tag
      const companies = await this.prisma.company.findMany({
        where: {
          tags: { has: 'ERP' },
        },
        select: {
          id: true,
          tenantId: true,
          name: true,
          lexwareContactId: true,
        },
      });

      let pushCount = 0;
      for (const company of companies) {
        try {
          await this.contactsService.pushCompanyToLexware(
            company.tenantId,
            company.id,
          );
          pushCount++;
        } catch (error) {
          this.logger.warn(
            `ERP-Push fehlgeschlagen fuer Company "${company.name}": ${error instanceof Error ? error.message : 'Unbekannt'}`,
          );
        }
      }

      // Contacts mit "ERP" Tag
      const contacts = await this.prisma.contact.findMany({
        where: {
          tags: { has: 'ERP' },
        },
        select: {
          id: true,
          tenantId: true,
          firstName: true,
          lastName: true,
          lexwareContactId: true,
        },
      });

      for (const contact of contacts) {
        try {
          await this.contactsService.pushContactToLexware(
            contact.tenantId,
            contact.id,
          );
          pushCount++;
        } catch (error) {
          this.logger.warn(
            `ERP-Push fehlgeschlagen fuer Contact "${contact.firstName} ${contact.lastName}": ${error instanceof Error ? error.message : 'Unbekannt'}`,
          );
        }
      }

      this.logger.log(
        `ERP-Push abgeschlossen. ${pushCount} von ${companies.length + contacts.length} Entitaeten gepusht.`,
      );
    } finally {
      await this.releaseLock(lockKey);
    }
  }

  // --------------------------------------------------------
  // Distributed Lock (Redis SET NX EX)
  // --------------------------------------------------------

  private async acquireLock(
    key: string,
    ttlSeconds: number,
  ): Promise<boolean> {
    try {
      const result = await this.redis.setNx(key, 'locked', ttlSeconds);
      return result;
    } catch {
      return false;
    }
  }

  private async releaseLock(key: string): Promise<void> {
    try {
      await this.redis.del(key);
    } catch {
      // Lock wird durch TTL automatisch freigegeben
    }
  }
}
