// ==================== PLESK EMAIL PROVIDER ====================

import { Agent, fetch as undiciFetch } from 'undici';
import {
  IEmailProvider,
  EmailProviderConfig,
  EmailExistsResult,
  EmailOperationResult,
  CreateEmailParams,
  CreateEmailWithMailboxParams,
  CreateEmailWithMailboxResult,
  EnableMailboxParams,
  UpdateMailboxPasswordParams,
  RenameEmailParams,
} from './types.js';

// Undici-Agent der selbstsignierte Zertifikate akzeptiert
// Mit Timeouts und Connection-Limits um Probleme zu vermeiden
const httpsAgent = new Agent({
  connect: {
    rejectUnauthorized: false,
    timeout: 10000, // 10 Sekunden Connect-Timeout
  },
  bodyTimeout: 30000, // 30 Sekunden für Response-Body
  headersTimeout: 30000, // 30 Sekunden für Headers
  keepAliveTimeout: 1000, // Connections nach 1 Sekunde schließen
  keepAliveMaxTimeout: 5000, // Maximal 5 Sekunden Keep-Alive
  connections: 1, // Nur eine Connection gleichzeitig pro Host
  pipelining: 1, // Kein Pipelining
});

export class PleskEmailProvider implements IEmailProvider {
  readonly type = 'PLESK';
  private config: EmailProviderConfig;

  constructor(config: EmailProviderConfig) {
    this.config = config;
  }

  // Basis-URL für API-Requests
  private get baseUrl(): string {
    // Entferne trailing slash falls vorhanden
    return this.config.apiUrl.replace(/\/$/, '');
  }

  // HTTP-Request an Plesk API senden
  private async request<T>(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE',
    endpoint: string,
    data?: Record<string, unknown>
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;

    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    };

    // Authentifizierung: API-Key hat Priorität, sonst Basic Auth
    if (this.config.apiKey) {
      // Nur API-Key verwenden (ohne Basic Auth)
      headers['X-API-Key'] = this.config.apiKey;
    } else if (this.config.username && this.config.password) {
      // Basic Auth nur wenn kein API-Key
      const authHeader = Buffer.from(
        `${this.config.username}:${this.config.password}`
      ).toString('base64');
      headers['Authorization'] = `Basic ${authHeader}`;
    } else {
      // Keine Authentifizierung vorhanden
      throw new Error('Keine Zugangsdaten angegeben - bitte API-Key oder Benutzername/Passwort eingeben');
    }

    const options: Parameters<typeof undiciFetch>[1] = {
      method,
      headers,
      dispatcher: httpsAgent,
    };

    if (data && (method === 'POST' || method === 'PUT')) {
      options.body = JSON.stringify(data);
    }

    try {
      const response = await undiciFetch(url, options);

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Plesk API Fehler: ${response.status} - ${errorText}`);
      }

      // Leere Response bei DELETE
      if (response.status === 204) {
        return {} as T;
      }

      return await response.json() as T;
    } catch (error) {
      // Verbesserte Fehlermeldungen für häufige Probleme
      if (error instanceof Error) {
        const msg = error.message.toLowerCase();

        // Netzwerkfehler
        if (msg.includes('econnrefused')) {
          throw new Error(`Server nicht erreichbar unter ${this.baseUrl} - Ist der Server gestartet?`);
        }
        if (msg.includes('enotfound') || msg.includes('getaddrinfo')) {
          throw new Error(`Server-Adresse nicht gefunden: ${this.baseUrl} - Bitte URL prüfen`);
        }
        if (msg.includes('etimedout') || msg.includes('timeout')) {
          throw new Error(`Zeitüberschreitung bei Verbindung zu ${this.baseUrl}`);
        }
        if (msg.includes('econnreset')) {
          throw new Error(`Verbindung wurde vom Server abgebrochen`);
        }

        // SSL/TLS Fehler
        if (msg.includes('cert') || msg.includes('ssl') || msg.includes('tls') || msg.includes('unable_to_verify')) {
          throw new Error(`SSL-Zertifikatsfehler - Selbstsigniertes Zertifikat wird nicht akzeptiert`);
        }

        // fetch failed ist meist ein Netzwerk/SSL Problem
        if (msg.includes('fetch failed')) {
          throw new Error(`Verbindung fehlgeschlagen zu ${this.baseUrl} - Bitte prüfen: Server erreichbar? HTTPS-Port korrekt?`);
        }
      }

      console.error('Plesk API Request failed:', error);
      throw error;
    }
  }

  async testConnection(): Promise<void> {
    // Versuche Server-Info abzurufen - wirft Fehler bei Auth-Problemen
    try {
      await this.request('GET', '/api/v2/server');
    } catch (error) {
      if (error instanceof Error) {
        // Verbesserte Fehlermeldung
        if (error.message.includes('401')) {
          throw new Error('Authentifizierung fehlgeschlagen - Benutzername/Passwort oder API-Key prüfen');
        }
        if (error.message.includes('403')) {
          throw new Error('Zugriff verweigert - Berechtigungen prüfen');
        }
        // Andere Fehler wurden schon in request() übersetzt
      }
      throw error;
    }
  }

  async emailExists(localPart: string): Promise<EmailExistsResult> {
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Plesk CLI API: Mail-Info abfragen
      const result = await this.request<{ code: number; stdout: string; stderr: string }>(
        'POST',
        '/api/v2/cli/mail/call',
        { params: ['--info', email] }
      );

      // Debug: Response-Struktur loggen
      console.log('Plesk emailExists response:', JSON.stringify(result, null, 2));

      // Plesk gibt code=0 bei Erfolg, code!=0 bei Fehler
      // stderr enthält Fehlermeldung wenn Mail nicht existiert
      const hasError = result.code !== 0 ||
                       result.stderr?.toLowerCase().includes('not found') ||
                       result.stderr?.toLowerCase().includes('does not exist') ||
                       result.stderr?.toLowerCase().includes('unable to find') ||
                       result.stderr?.toLowerCase().includes('no such');

      if (hasError) {
        return { exists: false };
      }

      // stdout sollte die Mail-Infos enthalten
      const exists = result.stdout?.toLowerCase().includes(localPart.toLowerCase());

      // Mailbox-Status aus stdout parsen (Format: "Mailbox: true" oder "Mailbox: false")
      let hasMailbox: boolean | undefined;
      let mailgroupActive: boolean | undefined;
      let mailgroupMembers: string[] | undefined;
      let forwardingActive: boolean | undefined;
      let forwardingTargets: string[] | undefined;
      if (exists && result.stdout) {
        const mailboxMatch = result.stdout.match(/Mailbox:\s*(true|false)/i);
        if (mailboxMatch) {
          hasMailbox = mailboxMatch[1].toLowerCase() === 'true';
        }

        // Mailgroup-Status + Mitglieder. Plesk listet sie auf einer
        // Zeile, Adressen sind durch Whitespace getrennt.
        const mailgroupMatch = result.stdout.match(/Mailgroup:\s*(true|false)/i);
        if (mailgroupMatch) {
          mailgroupActive = mailgroupMatch[1].toLowerCase() === 'true';
        }
        const groupMembersMatch = result.stdout.match(/Group member\(s\):\s*([^\n]*)/i);
        if (groupMembersMatch) {
          mailgroupMembers = groupMembersMatch[1]
            .trim()
            .split(/\s+/)
            .filter((m) => m.includes('@'));
        }

        // Forwarding-Status + Ziele. Plesk druckt "Forward request: <addrs>".
        // Auf manchen Plesk-Versionen heißt das Feld auch "Forwarding".
        const forwardActiveMatch = result.stdout.match(/Forwarding:\s*(true|false)/i);
        if (forwardActiveMatch) {
          forwardingActive = forwardActiveMatch[1].toLowerCase() === 'true';
        }
        const forwardTargetsMatch = result.stdout.match(/Forward(?:ing)?(?: request)?:\s*([^\n]*)/i);
        if (forwardTargetsMatch) {
          forwardingTargets = forwardTargetsMatch[1]
            .trim()
            .split(/\s+/)
            .filter((m) => m.includes('@'));
          if (forwardingActive === undefined) {
            forwardingActive = (forwardingTargets?.length ?? 0) > 0;
          }
        }
      }

      return {
        exists,
        email: exists ? email : undefined,
        hasMailbox,
        mailgroupActive,
        mailgroupMembers,
        forwardingActive,
        forwardingTargets,
      };
    } catch (error) {
      // HTTP-Fehler oder Netzwerkfehler
      if (error instanceof Error) {
        const msg = error.message.toLowerCase();
        // "not found" = Mail gibt es nicht
        if (msg.includes('not found') || msg.includes('does not exist') || msg.includes('unable to find')) {
          return { exists: false };
        }
      }
      console.error('Plesk emailExists error:', error);
      return { exists: false };
    }
  }

  async createEmail(params: CreateEmailParams): Promise<EmailOperationResult> {
    const { localPart, forwardTargets } = params;
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob schon existiert
      const exists = await this.emailExists(localPart);
      if (exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} existiert bereits`,
        };
      }

      // Plesk CLI API: Mail-Account mit Weiterleitung erstellen
      // Verwendet den CLI-Wrapper unter /api/v2/cli/mail/call
      // Format für -forwarding-addresses: "add:email1,email2" oder "set:email1,email2"
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: [
          '--create', email,
          '-forwarding', 'true',
          '-forwarding-addresses', `add:${forwardTargets.join(',')}`,
          '-mailbox', 'false',
        ],
      });

      return {
        success: true,
        message: `E-Mail ${email} erfolgreich erstellt mit Weiterleitung an: ${forwardTargets.join(', ')}`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk createEmail error:', error);
      return {
        success: false,
        error: `Fehler beim Erstellen der E-Mail: ${errorMessage}`,
      };
    }
  }

  async createEmailWithMailbox(params: CreateEmailWithMailboxParams): Promise<CreateEmailWithMailboxResult> {
    const { localPart, forwardTargets, password } = params;
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob schon existiert
      const exists = await this.emailExists(localPart);
      if (exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} existiert bereits`,
        };
      }

      // Plesk CLI API: Mail-Account mit echter Mailbox erstellen
      // -mailbox true: Echte Mailbox (IMAP/SMTP-Zugang)
      // -passwd: Passwort für die Mailbox
      // -forwarding true: Zusätzlich Weiterleitung aktivieren
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: [
          '--create', email,
          '-mailbox', 'true',
          '-passwd', password,
          '-forwarding', 'true',
          '-forwarding-addresses', `add:${forwardTargets.join(',')}`,
        ],
      });

      return {
        success: true,
        message: `E-Mail ${email} mit Mailbox erfolgreich erstellt`,
        email,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk createEmailWithMailbox error:', error);
      return {
        success: false,
        error: `Fehler beim Erstellen der E-Mail mit Mailbox: ${errorMessage}`,
      };
    }
  }

  async enableMailboxForExisting(params: EnableMailboxParams): Promise<EmailOperationResult> {
    const { localPart, password } = params;
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob E-Mail existiert
      const exists = await this.emailExists(localPart);
      if (!exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} nicht gefunden`,
        };
      }

      // Plesk CLI API: Mailbox für existierende E-Mail aktivieren
      // --update: Existierende E-Mail aktualisieren
      // -mailbox true: Mailbox aktivieren
      // -passwd: Passwort für die Mailbox setzen
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: [
          '--update', email,
          '-mailbox', 'true',
          '-passwd', password,
        ],
      });

      return {
        success: true,
        message: `Mailbox für ${email} erfolgreich aktiviert`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk enableMailboxForExisting error:', error);
      return {
        success: false,
        error: `Fehler beim Aktivieren der Mailbox: ${errorMessage}`,
      };
    }
  }

  async updateMailboxPassword(params: UpdateMailboxPasswordParams): Promise<EmailOperationResult> {
    const { localPart, password } = params;
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob E-Mail existiert
      const exists = await this.emailExists(localPart);
      if (!exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} nicht gefunden`,
        };
      }

      // Plesk CLI API: Passwort für existierende E-Mail aktualisieren
      // --update: Existierende E-Mail aktualisieren
      // -passwd: Neues Passwort setzen
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: [
          '--update', email,
          '-passwd', password,
        ],
      });

      return {
        success: true,
        message: `Passwort für ${email} erfolgreich aktualisiert`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk updateMailboxPassword error:', error);
      return {
        success: false,
        error: `Fehler beim Aktualisieren des Passworts: ${errorMessage}`,
      };
    }
  }

  async deleteEmail(localPart: string): Promise<EmailOperationResult> {
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob Mail existiert
      const exists = await this.emailExists(localPart);
      if (!exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} nicht gefunden`,
        };
      }

      // Plesk CLI API: Mail-Account löschen
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: ['--remove', email],
      });

      return {
        success: true,
        message: `E-Mail ${email} erfolgreich gelöscht`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk deleteEmail error:', error);
      return {
        success: false,
        error: `Fehler beim Löschen der E-Mail: ${errorMessage}`,
      };
    }
  }

  async renameEmail(params: RenameEmailParams): Promise<EmailOperationResult> {
    const { oldLocalPart, newLocalPart } = params;
    const oldEmail = `${oldLocalPart}@${this.config.domain}`;
    const newEmail = `${newLocalPart}@${this.config.domain}`;

    try {
      // Prüfen ob alte Mail existiert
      const oldExists = await this.emailExists(oldLocalPart);
      if (!oldExists.exists) {
        return {
          success: false,
          error: `E-Mail ${oldEmail} nicht gefunden`,
        };
      }

      // Prüfen ob neue Adresse schon existiert
      const newExists = await this.emailExists(newLocalPart);
      if (newExists.exists) {
        return {
          success: false,
          error: `E-Mail ${newEmail} existiert bereits`,
        };
      }

      // Plesk CLI API: Mail-Account umbenennen
      await this.request('POST', '/api/v2/cli/mail/call', {
        params: ['--rename', oldEmail, '-new-name', newLocalPart],
      });

      return {
        success: true,
        message: `E-Mail erfolgreich umbenannt von ${oldEmail} zu ${newEmail}`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk renameEmail error:', error);
      return {
        success: false,
        error: `Fehler beim Umbenennen der E-Mail: ${errorMessage}`,
      };
    }
  }

  async updateForwardTargets(
    localPart: string,
    targets: string[]
  ): Promise<EmailOperationResult> {
    const email = `${localPart}@${this.config.domain}`;

    try {
      // Prüfen ob Mail existiert
      const exists = await this.emailExists(localPart);
      if (!exists.exists) {
        return {
          success: false,
          error: `E-Mail ${email} nicht gefunden`,
        };
      }

      // Plesk-CLI-Eigenheit: `-forwarding-addresses` akzeptiert NUR
      // `add:` und `del:`, KEIN `set:`. Und `-forwarding` ist der
      // Mailgroup-Schalter (Plesk nennt das im `--info` "Mailgroup",
      // im CLI "forwarding" – derselbe Mechanismus, doppelt benannt).
      // Es gibt KEINE separaten Mailgroup-Optionen wie `-mailgroup`.
      //
      // Wir bauen daher den Diff: alte Member abrufen, dann
      // del:<entfernt> + add:<neu> in zwei separaten Calls. Idempotent,
      // weil add: Duplikate ignoriert und del: nicht-vorhandene auch.
      const currentMembers = exists.mailgroupMembers ?? [];
      const targetsLower = new Set(targets.map((t) => t.toLowerCase()));
      const currentLower = new Set(currentMembers.map((m) => m.toLowerCase()));
      const toRemove = currentMembers.filter((m) => !targetsLower.has(m.toLowerCase()));
      const toAdd = targets.filter((t) => !currentLower.has(t.toLowerCase()));

      console.log(
        `[Plesk updateForwardTargets] ${email} – aktuell: [${currentMembers.join(', ')}], ` +
        `soll: [${targets.join(', ')}], entfernen: [${toRemove.join(', ')}], hinzufügen: [${toAdd.join(', ')}]`,
      );

      // Entfernen-Schritt
      if (toRemove.length > 0) {
        const delParams = [
          '--update', email,
          '-forwarding-addresses', `del:${toRemove.join(',')}`,
        ];
        const delResult = await this.request<{ code: number; stdout: string; stderr: string }>(
          'POST', '/api/v2/cli/mail/call', { params: delParams },
        );
        console.log('[Plesk updateForwardTargets] del response:', JSON.stringify(delResult, null, 2));
        if (delResult.code !== 0 || /error|failed/i.test(delResult.stderr || '')) {
          return {
            success: false,
            error: delResult.stderr?.trim() || delResult.stdout?.trim() || `Plesk del returned code ${delResult.code}`,
          };
        }
      }

      // Hinzufügen-Schritt (impliziert -forwarding true, damit Mailgroup
      // aktiviert bleibt bzw. wird).
      if (toAdd.length > 0) {
        const addParams = [
          '--update', email,
          '-forwarding', 'true',
          '-forwarding-addresses', `add:${toAdd.join(',')}`,
        ];
        const addResult = await this.request<{ code: number; stdout: string; stderr: string }>(
          'POST', '/api/v2/cli/mail/call', { params: addParams },
        );
        console.log('[Plesk updateForwardTargets] add response:', JSON.stringify(addResult, null, 2));
        if (addResult.code !== 0 || /error|failed/i.test(addResult.stderr || '')) {
          return {
            success: false,
            error: addResult.stderr?.trim() || addResult.stdout?.trim() || `Plesk add returned code ${addResult.code}`,
          };
        }
      }

      return {
        success: true,
        message: `Weiterleitungen für ${email} aktualisiert: ${targets.join(', ')}`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
      console.error('Plesk updateForwardTargets error:', error);
      return {
        success: false,
        error: `Fehler beim Aktualisieren der Weiterleitungen: ${errorMessage}`,
      };
    }
  }
}
