import { SMTPServer } from 'smtp-server';
import { simpleParser, type ParsedMail } from 'mailparser';
import type { DispatchService } from '../messaging/dispatch-service.js';
import type { ApplicationRegistry } from '../core/application-registry.js';
import type { SendRequest } from '@zyphon/shared-types';

export interface SmtpRelayConfig {
  port?: number;
  host?: string;
  authRequired?: boolean;
}

export class InboundSmtpRelay {
  private server: SMTPServer;
  private dispatchService: DispatchService;
  private appRegistry: ApplicationRegistry;
  private port: number;
  private host: string;
  private isRunning = false;

  constructor(
    dispatchService: DispatchService,
    appRegistry: ApplicationRegistry,
    config: SmtpRelayConfig = {}
  ) {
    this.dispatchService = dispatchService;
    this.appRegistry = appRegistry;
    this.port = config.port || 2525;
    this.host = config.host || '0.0.0.0';

    this.server = new SMTPServer({
      authOptional: !config.authRequired,
      disabledCommands: ['STARTTLS'], // Simple plain relay for internal networks

      onAuth: (auth, _session, callback) => {
        const apiKey = auth.password || auth.username || '';
        const app = apiKey ? this.appRegistry.authenticateKey(apiKey) : null;
        if (app) {
          return callback(null, { user: app.id });
        }
        if (!config.authRequired) {
          return callback(null, { user: 'anonymous' });
        }
        return callback(new Error('Invalid credentials'));
      },

      onData: (stream, session, callback) => {
        let apiKey = '';
        if (session.user) {
          // If authenticated, we have app.id
          // Get the first active key for app
          const app = this.appRegistry.getApplication(session.user);
          if (app) {
            const keys = (this.appRegistry as any).appKeys?.get(app.id);
            if (keys && keys.length > 0) {
              // We dispatch with authenticated app context
            }
          }
        }

        simpleParser(stream, async (err: Error | null, parsed: ParsedMail) => {
          if (err) {
            return callback(new Error('Failed to parse incoming email stream'));
          }

          const recipient = Array.isArray(parsed.to)
            ? (parsed.to[0]?.value[0]?.address || '')
            : (parsed.to?.value[0]?.address || '');

          if (!recipient) {
            return callback(new Error('Missing recipient in incoming email'));
          }

          const sendRequest: SendRequest = {
            channel: 'email',
            recipient,
            content: {
              type: parsed.html ? 'html' : 'text',
              subject: parsed.subject || '(No Subject)',
              body: (parsed.html || parsed.text || '').toString(),
              attachments: parsed.attachments?.map((att) => ({
                filename: att.filename || 'attachment',
                content: att.content.toString('base64'),
                contentType: att.contentType,
              })),
            },
            metadata: {
              source: 'inbound_smtp_relay',
              sender: parsed.from?.text,
            },
          };

          try {
            // Find default or first app for relay if none authenticated
            const allApps = this.appRegistry.getAllApplications();
            const targetApp = (session.user && this.appRegistry.getApplication(session.user)) || allApps[0];

            if (!targetApp) {
              return callback(new Error('No registered application found for SMTP relay'));
            }

            // We can dispatch via dispatch service
            // Create a temporary key validation or generate one if needed
            let activeApiKey = apiKey;
            if (!activeApiKey) {
              const gen = this.appRegistry.generateKey(targetApp.id);
              activeApiKey = gen.api_key;
            }

            const result = await this.dispatchService.dispatch(sendRequest, {
              apiKey: activeApiKey,
              sync: true,
            });

            if (result.statusCode >= 200 && result.statusCode < 300) {
              return callback(); // 250 OK
            } else {
              return callback(new Error(result.response.error?.message || 'Dispatch rejected'));
            }
          } catch (dispatchErr: any) {
            return callback(new Error(dispatchErr.message || 'Internal relay error'));
          }
        });
      },
    });

    this.server.on('error', (err) => {
      // Log server errors
      console.error('[SMTP-RELAY] Server error:', err.message);
    });
  }

  public async start(): Promise<void> {
    if (this.isRunning) return;

    await new Promise<void>((resolve, reject) => {
      this.server.listen(this.port, this.host, (err?: Error) => {
        if (err) reject(err);
        else {
          this.isRunning = true;
          resolve();
        }
      });
    });
  }

  public async stop(): Promise<void> {
    if (!this.isRunning) return;

    await new Promise<void>((resolve) => {
      this.server.close(() => {
        this.isRunning = false;
        resolve();
      });
    });
  }
}
