import type { IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
import { EmailServerSettingsDoc } from '../db/index.js';
import type { IDcRouterOptions } from '../classes.dcrouter.js';
import type {
  IEmailPortConfig,
  IEmailServerSettings,
  TEmailServerSettingsUpdate,
} from '../../ts_interfaces/data/email-settings.js';

const defaultEmailPorts = [25, 587, 465];

function clonePlain<T>(value: T | undefined): T | undefined {
  if (value === undefined) return undefined;
  return JSON.parse(JSON.stringify(value)) as T;
}

function hasOwn(objectArg: object, keyArg: string): boolean {
  return Object.prototype.hasOwnProperty.call(objectArg, keyArg);
}

export class EmailSettingsManager {
  private cachedEmailConfig?: IUnifiedEmailServerOptions;
  private cachedEmailPortConfig?: IEmailPortConfig;
  private enabled = false;
  private updatedAt = 0;
  private updatedBy = 'default';

  constructor(private options: IDcRouterOptions) {}

  public async start(): Promise<void> {
    let doc = await EmailServerSettingsDoc.load();

    if (!doc) {
      doc = new EmailServerSettingsDoc();
      doc.settingsId = 'email-server-settings';
      doc.enabled = false;
      doc.updatedAt = Date.now();
      doc.updatedBy = 'default';
      await doc.save();
    }

    this.loadFromDoc(doc);
    this.applyToRuntimeOptions();
  }

  public async stop(): Promise<void> {
    this.cachedEmailConfig = undefined;
    this.cachedEmailPortConfig = undefined;
    this.enabled = false;
  }

  public isEnabled(): boolean {
    return this.enabled && Boolean(this.cachedEmailConfig);
  }

  public getEmailConfig(): IUnifiedEmailServerOptions | undefined {
    return this.isEnabled() ? clonePlain(this.cachedEmailConfig) : undefined;
  }

  public getEmailPortConfig(): IEmailPortConfig | undefined {
    return this.isEnabled() ? clonePlain(this.cachedEmailPortConfig) : undefined;
  }

  public getPublicSettings(): IEmailServerSettings {
    const emailConfig = this.cachedEmailConfig;
    const emailPortConfig = this.cachedEmailPortConfig;
    return {
      enabled: this.isEnabled(),
      hostname: emailConfig?.hostname || null,
      ports: [...(emailConfig?.ports || [])],
      portMapping: emailPortConfig?.portMapping ? { ...emailPortConfig.portMapping } : null,
      receivedEmailsPath: emailPortConfig?.receivedEmailsPath || null,
      maxMessageSize: emailConfig?.maxMessageSize ?? null,
      domainCount: emailConfig?.domains?.length || 0,
      routeCount: emailConfig?.routes?.length || 0,
      authUserCount: emailConfig?.auth?.users?.length || 0,
      updatedAt: this.updatedAt,
      updatedBy: this.updatedBy,
    };
  }

  public async updateSettings(
    updates: TEmailServerSettingsUpdate,
    updatedBy: string,
  ): Promise<IEmailServerSettings> {
    let doc = await EmailServerSettingsDoc.load();
    if (!doc) {
      doc = new EmailServerSettingsDoc();
      doc.settingsId = 'email-server-settings';
    }

    const nextEnabled = hasOwn(updates, 'enabled') ? Boolean(updates.enabled) : doc.enabled;
    const nextEmailConfig = this.patchEmailConfig(doc.emailConfig, updates, nextEnabled);
    const nextEmailPortConfig = this.patchEmailPortConfig(doc.emailPortConfig, updates);

    doc.enabled = nextEnabled;
    doc.emailConfig = nextEmailConfig;
    doc.emailPortConfig = nextEmailPortConfig;
    doc.updatedAt = Date.now();
    doc.updatedBy = updatedBy;
    await doc.save();

    this.loadFromDoc(doc);
    this.applyToRuntimeOptions();
    return this.getPublicSettings();
  }

  private loadFromDoc(doc: EmailServerSettingsDoc): void {
    this.enabled = doc.enabled;
    this.cachedEmailConfig = clonePlain(doc.emailConfig);
    this.cachedEmailPortConfig = clonePlain(doc.emailPortConfig);
    this.updatedAt = doc.updatedAt;
    this.updatedBy = doc.updatedBy;
  }

  private applyToRuntimeOptions(): void {
    this.options.emailConfig = this.getEmailConfig();
    this.options.emailPortConfig = this.getEmailPortConfig();
  }

  private patchEmailConfig(
    existingConfig: IUnifiedEmailServerOptions | undefined,
    updates: TEmailServerSettingsUpdate,
    nextEnabled: boolean,
  ): IUnifiedEmailServerOptions | undefined {
    const nextConfig: IUnifiedEmailServerOptions | undefined = clonePlain(existingConfig) || (nextEnabled ? {
      hostname: 'localhost',
      ports: [...defaultEmailPorts],
      domains: [],
      routes: [],
    } : undefined);

    if (!nextConfig) return undefined;

    if (hasOwn(updates, 'hostname')) {
      const hostname = updates.hostname?.trim() || '';
      if (nextEnabled && !hostname) {
        throw new Error('Email hostname is required when email is enabled');
      }
      nextConfig.hostname = hostname || nextConfig.hostname;
    }

    if (hasOwn(updates, 'ports')) {
      nextConfig.ports = this.normalizePorts(updates.ports || []);
    }

    if (hasOwn(updates, 'maxMessageSize')) {
      if (updates.maxMessageSize === null || updates.maxMessageSize === undefined) {
        delete nextConfig.maxMessageSize;
      } else {
        const maxMessageSize = Number(updates.maxMessageSize);
        if (!Number.isInteger(maxMessageSize) || maxMessageSize <= 0) {
          throw new Error('maxMessageSize must be a positive integer');
        }
        nextConfig.maxMessageSize = maxMessageSize;
      }
    }

    if (nextEnabled) {
      if (!nextConfig.hostname?.trim()) {
        throw new Error('Email hostname is required when email is enabled');
      }
      nextConfig.ports = this.normalizePorts(nextConfig.ports || []);
    }

    nextConfig.domains = nextConfig.domains || [];
    nextConfig.routes = nextConfig.routes || [];
    return nextConfig;
  }

  private patchEmailPortConfig(
    existingPortConfig: IEmailPortConfig | undefined,
    updates: TEmailServerSettingsUpdate,
  ): IEmailPortConfig | undefined {
    const nextPortConfig: IEmailPortConfig = clonePlain(existingPortConfig) || {};
    if (hasOwn(updates, 'portMapping')) {
      if (updates.portMapping === null) {
        delete nextPortConfig.portMapping;
      } else {
        nextPortConfig.portMapping = this.normalizePortMapping(updates.portMapping || {});
      }
    }
    if (hasOwn(updates, 'receivedEmailsPath')) {
      const receivedEmailsPath = updates.receivedEmailsPath?.trim() || '';
      if (receivedEmailsPath) {
        nextPortConfig.receivedEmailsPath = receivedEmailsPath;
      } else {
        delete nextPortConfig.receivedEmailsPath;
      }
    }
    return Object.keys(nextPortConfig).length > 0 ? nextPortConfig : undefined;
  }

  private normalizePorts(ports: number[]): number[] {
    const normalized = [...new Set(ports.map((port) => Number(port)))];
    if (normalized.length === 0) {
      throw new Error('At least one email port is required when email is enabled');
    }
    for (const port of normalized) {
      if (!Number.isInteger(port) || port < 1 || port > 65535) {
        throw new Error(`Invalid email port: ${port}`);
      }
    }
    return normalized.sort((a, b) => a - b);
  }

  private normalizePortMapping(portMapping: Record<number, number>): Record<number, number> {
    const normalized: Record<number, number> = {};
    for (const [externalPortString, internalPortValue] of Object.entries(portMapping)) {
      const externalPort = Number(externalPortString);
      const internalPort = Number(internalPortValue);
      for (const port of [externalPort, internalPort]) {
        if (!Number.isInteger(port) || port < 1 || port > 65535) {
          throw new Error(`Invalid email port mapping value: ${port}`);
        }
      }
      normalized[externalPort] = internalPort;
    }
    return normalized;
  }
}
