import {
  Injectable,
  Logger,
  NotFoundException,
  InternalServerErrorException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
import { PrismaService } from '../../prisma/prisma.service';
import { EntraIdService, type M365TokenResult } from '../auth/sso/entra-id.service';

/** Antwort fuer den Frontend (ohne Tokens) */
export interface IntegrationInfo {
  provider: 'MICROSOFT_365';
  connected: boolean;
  connectedAt?: string;
  expiresAt?: string;
  scopes?: string[];
}

/** Antwort fuer den internen Token-Endpoint (CRM-Service) */
export interface TokenResponse {
  accessToken: string;
  expiresAt: string;
  provider: string;
}

const ALGORITHM = 'aes-256-gcm';

@Injectable()
export class IntegrationsService {
  private readonly logger = new Logger(IntegrationsService.name);
  private readonly encryptionKey: Buffer;

  constructor(
    private readonly prisma: PrismaService,
    private readonly entraId: EntraIdService,
    private readonly config: ConfigService,
  ) {
    // Encryption Key aus Env (64 Hex-Zeichen = 32 Bytes)
    const keyHex = this.config.get<string>('INTEGRATION_ENCRYPTION_KEY');
    if (keyHex && keyHex.length === 64) {
      this.encryptionKey = Buffer.from(keyHex, 'hex');
    } else {
      // Fallback: Deterministisch aus JWT-Key ableiten (Development only)
      this.logger.warn(
        'INTEGRATION_ENCRYPTION_KEY nicht konfiguriert — verwende unsicheren Fallback (nur fuer Entwicklung!)',
      );
      // Fallback-Key: 32 Bytes aus festem String (NIE in Produktion)
      this.encryptionKey = Buffer.alloc(32, 'insight-dev-fallback-key-32bytes');
    }
  }

  // ── Verschluesselung ──────────────────────────────────────────────────

  private encrypt(text: string): string {
    const iv = randomBytes(12); // 96-bit IV fuer GCM
    const cipher = createCipheriv(ALGORITHM, this.encryptionKey, iv);
    const encrypted = Buffer.concat([
      cipher.update(text, 'utf8'),
      cipher.final(),
    ]);
    const tag = cipher.getAuthTag();
    // Format: iv:tag:encrypted (alle hex-kodiert, ':'-separiert)
    return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`;
  }

  private decrypt(encryptedText: string): string {
    const parts = encryptedText.split(':');
    if (parts.length !== 3) throw new Error('Ungueltig verschluesselter Wert');
    const [ivHex, tagHex, dataHex] = parts;
    const iv = Buffer.from(ivHex, 'hex');
    const tag = Buffer.from(tagHex, 'hex');
    const data = Buffer.from(dataHex, 'hex');
    const decipher = createDecipheriv(ALGORITHM, this.encryptionKey, iv);
    decipher.setAuthTag(tag);
    return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
  }

  // ── CRUD ──────────────────────────────────────────────────────────────

  /**
   * M365-Integration nach OAuth-Callback speichern.
   */
  async saveM365Integration(userId: string, tokenResult: M365TokenResult): Promise<void> {
    const data = {
      provider: 'MICROSOFT_365',
      accessToken: this.encrypt(tokenResult.accessToken),
      refreshToken: this.encrypt(tokenResult.refreshToken || 'NONE'),
      expiresAt: tokenResult.expiresAt,
      scopes: tokenResult.scopes,
      msTenantId: tokenResult.msTenantId || null,
    };

    await this.prisma.userIntegration.upsert({
      where: { userId_provider: { userId, provider: 'MICROSOFT_365' } },
      create: { userId, ...data },
      update: data,
    });

    this.logger.log(`M365-Integration gespeichert fuer User ${userId}`);
  }

  /**
   * M365-Token holen (automatisches Refresh wenn abgelaufen).
   * Wird vom internen Token-Endpoint fuer den CRM-Service aufgerufen.
   */
  async getM365Token(userId: string): Promise<TokenResponse> {
    const integration = await this.prisma.userIntegration.findUnique({
      where: { userId_provider: { userId, provider: 'MICROSOFT_365' } },
    });

    if (!integration) {
      throw new NotFoundException(
        'Keine Microsoft 365 Integration fuer diesen User',
      );
    }

    const now = new Date();
    const bufferMs = 5 * 60 * 1000; // 5 Minuten Puffer

    // Token noch gueltig
    if (integration.expiresAt.getTime() - now.getTime() > bufferMs) {
      return {
        accessToken: this.decrypt(integration.accessToken),
        expiresAt: integration.expiresAt.toISOString(),
        provider: 'MICROSOFT_365',
      };
    }

    // Token abgelaufen — refresh
    this.logger.log(`M365-Token abgelaufen fuer User ${userId} — refreshing`);
    const refreshToken = this.decrypt(integration.refreshToken);

    if (refreshToken === 'NONE' || !refreshToken) {
      throw new InternalServerErrorException(
        'Kein Refresh-Token vorhanden — bitte Microsoft 365 neu verbinden',
      );
    }

    try {
      const refreshed = await this.entraId.refreshIntegrationToken(refreshToken);

      // Aktualisierte Tokens speichern
      await this.prisma.userIntegration.update({
        where: { userId_provider: { userId, provider: 'MICROSOFT_365' } },
        data: {
          accessToken: this.encrypt(refreshed.accessToken),
          refreshToken: refreshed.newRefreshToken
            ? this.encrypt(refreshed.newRefreshToken)
            : integration.refreshToken,
          expiresAt: refreshed.expiresAt,
        },
      });

      return {
        accessToken: refreshed.accessToken,
        expiresAt: refreshed.expiresAt.toISOString(),
        provider: 'MICROSOFT_365',
      };
    } catch (err) {
      this.logger.error(
        `M365-Token-Refresh fehlgeschlagen fuer User ${userId}: ${(err as Error).message}`,
      );
      throw new InternalServerErrorException(
        'Microsoft 365 Token konnte nicht erneuert werden — bitte neu verbinden',
      );
    }
  }

  /**
   * Alle Integrationen eines Users auflisten (ohne Tokens).
   */
  async listIntegrations(userId: string): Promise<IntegrationInfo[]> {
    const integrations = await this.prisma.userIntegration.findMany({
      where: { userId },
      select: {
        provider: true,
        expiresAt: true,
        scopes: true,
        createdAt: true,
      },
    });

    // M365 immer anzeigen (connected oder nicht)
    const m365 = integrations.find((i) => i.provider === 'MICROSOFT_365');
    return [
      {
        provider: 'MICROSOFT_365',
        connected: !!m365,
        connectedAt: m365?.createdAt.toISOString(),
        expiresAt: m365?.expiresAt.toISOString(),
        scopes: m365?.scopes ?? [],
      },
    ];
  }

  /**
   * M365-Integration trennen.
   */
  async removeM365Integration(userId: string): Promise<void> {
    await this.prisma.userIntegration.deleteMany({
      where: { userId, provider: 'MICROSOFT_365' },
    });
    this.logger.log(`M365-Integration entfernt fuer User ${userId}`);
  }
}
